Move JS and CSS files to a Javascript source dir (#156)

This commit is contained in:
gbrodman 2019-07-05 12:01:16 -04:00 committed by GitHub
parent 75c3792c4b
commit 153887df11
43 changed files with 8 additions and 17 deletions

View file

@ -0,0 +1,24 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Test existing solely to run the :check BUILD rule.
*/
goog.setTestOnly();
goog.require('goog.testing.jsunit');
function testNothing() {}

View file

@ -0,0 +1,84 @@
/** Admin Settings */
div#tlds div.tld {
width: 209px;
}
#newTld {
width: 187px;
margin-left: 0.5em;
}
div#tlds div.tld input,
div#tlds div.tld button[type=button] {
height: 27px;
line-height: 27px;
background: #ebebeb;
vertical-align: top;
border: none;
border-bottom: solid 3px white;
}
div#tlds div.tld input {
width: 169px;
margin: 0;
padding: 0;
color: #555;
padding-left: 5px ! important;
}
div#tlds.editing div.tld input[readonly] {
margin-left: 0.5em;
}
div#tlds.editing div.tld button[type=button] {
display: inline-block;
float: right;
margin-left: -2px;
width: 30px;
min-width: 30px;
height: 30px;
color: grey;
font-size: 1.1em;
}
div#tlds.editing div.tld button[type=button]:hover {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
div#tlds.editing div.tld button[type=button] i {
font-style: normal;
}
div#tlds.editing .kd-errormessage {
margin-left: 0.5em;
}
#ote-results-table {
margin-left: 0.5em;
margin-top: -5px;
border-top: 0;
padding-top: 0;
}
.ote-fulfilled {
background-color: #9df797;
}
.ote-semifulfilled {
background-color: #fcd18e;
}
.ote-unfulfilled {
background-color: #ffa9a9;
}
.ote-results-header {
height: 20px;
}
.ote-results-header-cell {
vertical-align: bottom;
}

View file

@ -0,0 +1,77 @@
.description {
display: block;
clear: both;
font-size: 12px;
color: #999;
line-height: 140%;
}
.whoAreYou {
width: 50%;
margin: 5em auto;
}
/* Console disabled page. */
.whoAreYou-disabled {
width: 50%;
margin: 5em auto;
}
.whoAreYou-disabled p img {
display: block;
margin: 3em auto;
}
.whoAreYou-disabled h1 {
border-top: solid 1px #ebebeb;
margin-top: 1em;
}
/* XXX: Should be re-enabled when search works. */
#kd-search {
display: none;
}
/* XXX: Should be generalized for use throughout console. */
div.domain-registrar-contact div.tooltip {
visibility: hidden;
/* XXX: Should have auto-width. */
width: 110px;
left: -55px;
height: 1em;
white-space: nowrap;
position: relative;
color: white;
background: #2d2d2d;
padding: 0.5em;
z-index: 2000;
margin-top: -30px;
font-style: normal;
}
div.domain-registrar-contact div.tooltip .pointer {
outline: none;
display: block;
position: relative;
bottom: -7px;
left: 55px;
margin: 0 0 0 -5px;
width: 0;
height: 0;
line-height: 0px;
font-size: 0px;
/* This sets the tooptip pointer color */
border-bottom: transparent;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #2d2d2d;
}
.reg-cryingAndroid {
float: right;
}
.reg-bullets {
padding-left: 1em;
list-style: disc inside;
}

View file

@ -0,0 +1,58 @@
.domain-registrar-contacts {
vertical-align: top;
text-align: top;
}
.domain-registrar-contact {
display: table-cell;
padding-right: 4em;
padding-bottom: 2em;
}
.domain-registrar-contact div {
margin: 0.5em 0;
}
.domain-registrar-contact + br {
display: table-row;
}
.domain-registrar-contact-name {
font-weight: bold;
display: inline;
padding-right: 30px;
}
.domain-registrar-contact-name i {
float: right;
width: 16px;
height: 16px;
}
.domain-registrar-contact-name i.domain-registrar-contact-visible-in-whois {
background: url('/assets/images/visibleOn_16.png') no-repeat right;
}
/** Postal style for address. */
td.setting-group-compact div.contact-address-city,
td.setting-group-compact div.contact-address-state,
td.setting-group-compact div.contact-address-zip,
td.setting-group-compact div.contact-address-cc {
width: initial;
display: inline;
}
/** Back to regular box flow for phone. */
td.setting-group-compact input#phoneNumber {
clear: both;
}
/**
* Vertical align shim for contact tds. Using border here instead of
* padding since this is a table and padding is ignored.
*
* @see td.setting p
*/
td.domain-registrar-contacts {
border-top: solid 0.5em white;
}

View file

@ -0,0 +1,16 @@
#domain-registrar-contact-us .description * {
color: #999 !important;
line-height: 140%;
}
#domain-registrar-contact-us p {
margin-bottom: 1.5em;
line-height: 140%;
}
#registry-phone {
width: 100%;
margin: 0;
padding: 1em;
background-color: #eaeaea;
}

View file

@ -0,0 +1,63 @@
#domain-registrar-dashboard {
text-align: center;
}
#domain-registrar-dashboard p {
color: grey;
}
#domain-registrar-dashboard super {
color: red;
font-size: 0.5em;
vertical-align: super;
font-weight: bold;
padding-left: 0.5em;
}
#domain-registrar-dashboard table {
border-collapse: collapse;
margin: 3em auto;
}
#domain-registrar-dashboard div.dashbox {
width: 230px;
min-width: 230px;
height: 260px;
padding: 1em 2em;
margin: 0 1.5em;
color: #777;
border-radius: 10px;
background-color: #f9f9f9;
text-align: center;
line-height: 140%;
font-size: 1.1em;
}
#domain-registrar-dashboard div.dashbox h2 {
font-weight: bold;
color: #555;
margin: 2em auto 1em auto;
}
#domain-registrar-dashboard div.dashbox a {
color: inherit;
}
#domain-registrar-dashboard table img {
display: block;
margin: 1em auto;
padding: 1em auto;
}
#domain-registrar-dashboard table + p {
margin: 2em auto;
width: 75%;
vertical-align: middle;
text-align: center;
}
#domain-registrar-dashboard p img {
margin-right: 1em;
vertical-align: middle;
text-align: center;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,61 @@
/* EPP styles */
.reg-add:before {
content: '+ ';
}
.contact h1 {
width: 650px;
background: url('/assets/images/ic_contacts_blue_12.png') no-repeat 0 0.7em;
padding-left: 2em;
margin-left: 0.75em;
}
/* Remove button. */
#contact-postalInfo table button {
margin: 1em;
float: right;
}
#contact-postalInfoHeader {
color: #777;
font-weight: normal;
}
#contact-postalInfo .info {
font-size: 11px;
color: #666;
}
#contact-postalInfo table {
margin: 0 0 1em 0;
padding: 1em;
}
#contact-postalInfo tr {
margin: auto 2em;
}
#contact-postalInfo tr:first-child {
margin-top: 1em;
}
#contact-postalInfo tr:last-child {
margin-bottom: 1em;
}
#contact-postalInfo table,
#contact-postalInfo input[readonly],
#contact-postalInfo textarea[readonly] {
background-color: #f1f1f1;
border-color: #f1f1f1;
}
#contact-postalInfoHeader button {
display: block;
margin: 1em 0;
}
#contact-postalInfoHeader button[disabled] {
display: none;
}

View file

@ -0,0 +1,187 @@
form.set,
form.item,
div.set,
div.item {
width: 700px;
}
.set table,
.item table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
/* Going to play some games here to get section leads to float left,
* followed by their next rows in floated right column. */
.item table,
.set table {
padding-top: 2em;
margin-top: 1em;
border-top: solid 1px #ebebeb;
}
.set tr,
.item tr {
margin: 0;
padding: 0;
min-height: 28px;
}
.set td,
.item td {
padding: 0.5em 0;
}
.item tr.section-lead th h2,
.item tr.section-lead th h3 {
font-size: 1em;
font-weight: bold;
color: #15c;
}
tr.subsection h3::first-letter {
text-transform: capitalize;
}
.set td:first-child,
.item td:first-child {
width: 260px;
}
.set td:first-child span.description,
.item td:first-child span.description {
width: 170px;
}
.description ol {
list-style-type: decimal;
margin-left: 2em;
}
/* Setting groups and labels. */
.set .kd-settings-pane-section td {
border-bottom: solid 1px #ebebeb;
}
td.setting-group-compact {
padding-left: 0.5em;
}
.setting-group-compact div {
line-height: 140%;
}
.item label {
font-weight: bold;
}
.set label + input {
margin-left: 1em;
}
.kd-settings-pane-section td label.setting-label {
padding-top: 0.5em;
}
td.label {
width: 70%;
}
/* Compact sequence of labels. */
.setting label + label {
margin-left: 2em;
}
form label,
.setting-label {
line-height: 13px;
margin-bottom: 5px;
}
.setting label {
display: inline-block;
font-weight: normal;
margin-right: 1em;
vertical-align: top;
}
div.checkbox-with-label {
margin-bottom: 1em;
}
/* Controls */
.set input:not([type="submit"]),
.item input:not([type="submit"]) {
width: 250px;
padding: 0.5em;
}
.item input[type='checkbox'],
.item input[type='radio'] {
width: auto;
}
input[readonly] {
border: solid 1px white;
margin-left: 0.5em;
padding-top: 0 !important;
padding-left: 0 !important;
}
.setting input[type=radio] {
}
.setting input[type=radio],
.setting input[type=checkbox] {
position: relative;
top: -3px;
}
/**
* Consistent top space in second column items. Input elements need to
* switch from using margin to padding when they're edit toggled, so that
* the text doesn't move. The other elements are just defined here for
* consistency.
* @see td.domain-registrar-contacts
*/
input[readonly],
td.setting p {
margin-top: 0.5em;
margin-left: 0.5em;
}
.item button[disabled] {
display: none;
}
div.checkbox-with-label input[type='checkbox'],
div.checkbox-with-label input[type='checkbox'] + label {
display: inline-block;
}
/*
* 3 line street has just inputs in a row
* @see td padding-bottom
*/
.setting input + input,
.setting div {
margin-top: 1em;
}
.setting div {
margin-top: 0.5em;
}
.setting input[type='checkbox'] {
display: inline;
}
.setting input + button,
.setting-item-list {
margin-left: 0.5em;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
@import 'admin-settings.css';
@import 'console.css';
@import 'contact-settings.css';
@import 'contact-us.css';
@import 'dashboard.css';
@import 'epp.css';
@import 'forms.css';
@import 'kd_components.css';
@import 'registry.css';
@import 'resources.css';
@import 'security-settings.css';

View file

@ -0,0 +1,298 @@
body {
overflow-x: hidden;
overflow-y: hidden !important;
font-family: Arial, sans-serif;
font-size: 13px;
color: #222;
}
h1 {
width: 100%;
font-size: 1.5em;
/* Bottom padding to get the paragraph text and Billing & resources to
* line up. */
padding: 0.75em 0.75em 3px 0;
}
#kd-social a,
#kd-social a:visited,
#reg-content a,
#reg-content a:visited {
color: #15c;
}
pre {
font-family: "Courier New", Courier, monospace;
white-space: pre-wrap;
}
table {
table-layout: fixed;
}
td {
vertical-align: top;
}
/* Set some explicit form styles so there's no jumping during toggle
* from readonly to editable. */
input, textarea {
border-style: solid;
border-color: lightgrey;
border-width: 1px;
}
input[readonly], textarea[readonly] {
resize: none;
border-color: white;
}
textarea {
margin-bottom: 1em;
font-family: "Courier New", Courier, monospace;
font-size: 13px;
border: solid 1px #ddd;
}
textarea[readonly] {
background-color: #eaeaea;
}
hr {
border: none;
border-top: solid 1px #ebebeb;
}
.hidden {
display: none;
}
#kd-googlebar {
position: fixed;
width: 100%;
min-width: 985px;
padding: 1em 2em;
white-space: nowrap;
height: 37px;
}
#kd-googlebar a.logo,
#kd-searchfield,
#kd-searchbutton {
position: static;
display: inline-block;
}
#kd-search {
width: 470px;
margin-top: 2px;
padding-top: 0px;
padding-left: 3em;
white-space: nowrap;
}
#kd-search form {
width: auto;
}
input#kd-searchfield,
#kd-searchbutton {
height: 29px;
border: none;
}
input#kd-searchfield {
width: 400px;
font-size: 12pt;
}
#kd-searchbutton {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#kd-social {
position: fixed;
top: 1.25em;
right: 0;
}
#kd-social .kd-name {
float: none;
color: #555;
}
.kd-name a:before {
content: ' | ';
color: #999;
}
/* Logo */
a.logo {
vertical-align: middle;
font-size: 30px;
font-weight: 300;
font-family: "open sans", sans-serif;
color: #63666a;
}
a.logo * {
vertical-align: middle;
}
/* Reg prefix. */
.reg-user-id {
display: block;
height: 1.5em;
padding: 0;
margin-top: -10px;
}
/* Misc. */
#loady {
position: absolute;
top: 5px;
right: -30px;
}
.eppResponse {
color: #333;
margin-top: 1em;
}
.kd-appbar {
padding: 1em 0;
position: fixed;
width: 100%;
}
li.kd-menulistitem {
text-transform: none;
}
#reg-nav.shown {
display: inherit;
}
/* Begin fixed headers and nav selectors */
#reg-app {
float: left;
margin-top: 64px;
width: 100%;
}
.kd-butterbar {
position: absolute;
display: block;
margin-left: inherit;
}
.kd-butterbar.shown {
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
}
.kd-appbar {
/* same as in reg-content below. lines the left edge of the
appbuttons and content area with the 'r' in registry. */
padding-left: 173px;
padding-top: .75em;
}
.kd-content-sidebar {
margin-left: 15px;
padding-left: 0;
border-left: 0;
}
#reg-nav {
position: fixed;
left: 0;
top: 136px;
width: 155px;
margin: 0 25px 0 0;
z-index: 3;
}
#reg-navlist li {
margin-left: 0;
padding-left: 0;
}
#reg-navlist li ul {
margin-left: 0em;
padding-left: 2em;
}
.reg-navlist-sub {
padding-left: 3px;
}
#reg-navlist a {
margin-left: 0;
padding-left: 2em;
border-left: solid 3px white;
}
#reg-navlist li ul,
#reg-navlist a {
line-height: 24px;
}
#reg-navlist li ul li {
margin-left: -2em;
}
#reg-navlist li ul li a {
padding-left: 3em;
}
#reg-navlist a.domain-active-nav {
border-left: solid 3px red;
font-weight: bold;
color: #bf624B;
}
#reg-content-and-footer {
position: absolute;
top: 136px;
left: 173px;
bottom: 0;
width: 100%;
margin: 0;
padding: 25px 0 1em 0;
overflow-y: scroll !important;
overflow-x: hidden;
}
#reg-content {
margin-bottom: 100px;
}
#reg-content,
.pageFooter {
width: 75%;
}
#debug {
position: absolute;
top: 127px;
right: 0;
width: 15%;
border-left: solid 1px grey;
padding-left: 1em;
}
/* End fixed headers and nav selectors */
#reg-content,
#reg-login {
min-height: 400px;
}
.reg-select {
margin-left: 23px;
}

View file

@ -0,0 +1,28 @@
#domain-registrar-resources h2 {
border-top: solid 1px #eee;
padding-top: 1em;
margin-top: 1em;
}
#domain-registrar-resources h2 img {
vertical-align: middle;
margin-right: 10px;
}
#domain-registrar-resources h2 + p,
#domain-registrar-resources button,
#domain-registrar-resources em {
margin-left: 34px; /* Folder icon + ^^ 10px more */
}
#domain-registrar-resources h2 + p {
padding-top: 1em;
}
#domain-registrar-resources a {
color: white !important;
}
#domain-registrar-resources em {
color: red;
}

View file

@ -0,0 +1,67 @@
/** Security Settings */
#domain-registrar-phone-passcode,
#domain-registrar-phone-passcode input {
color: #3D9200;
}
div#ips div.ip {
width: 209px;
}
#newIp {
width: 187px;
margin-left: 0.5em;
}
div#ips.editing div.ip input,
div#ips.editing div.ip button[type=button] {
display: inline-block;
height: 27px;
line-height: 27px;
background: #ebebeb;
vertical-align: top;
border: none;
border-bottom: solid 3px white;
}
div#ips.editing div.ip input {
width: 169px;
margin: 0;
padding: 0;
color: #555;
padding-left: 5px ! important;
}
div#ips.editing div.ip input[readonly] {
margin-left: 0.5em;
}
div#ips.editing div.ip button[type=button] {
float: right;
margin-left: -2px;
width: 30px;
min-width: 30px;
height: 30px;
color: grey;
font-size: 1.1em;
}
div#ips.editing div.ip button[type=button]:hover {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
div#ips.editing div.ip button[type=button] i {
font-style: normal;
}
div#ips.editing .kd-errormessage {
margin-left: 0.5em;
}
.item td.certificate h4 {
margin-top: 1em;
text-transform: lowercase;
border: solid 1px red;
}

View file

@ -0,0 +1,150 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview External JSON definitions. The purpose of this file is to give
* type information to the JavaScript compiler so it won't rename these
* properties.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.json = {};
registry.json.ote = {};
/**
* @typedef {{
* description: string,
* requirement: number,
* timesPerformed: number,
* completed: boolean
* }}
*/
registry.json.ote.OteStatusDetail;
/**
* @typedef {{
* clientId: string,
* completed: boolean,
* details: !Array.<registry.json.ote.OteStatusDetail>
* }}
*/
registry.json.ote.OteStatusResult;
/**
* @typedef {{
* status: string,
* message: string,
* results: !Array.<registry.json.ote.OteStatusResult>
* }}
*/
registry.json.ote.OteStatusResponse;
/**
* @constructor
* @template T
*/
registry.json.Response = function() {};
/**
* Request state which can be `SUCCESS` or `ERROR`.
* @type {string}
*/
registry.json.Response.prototype.status;
/**
* @type {string}
*/
registry.json.Response.prototype.message;
/**
* @type {string|undefined}
*/
registry.json.Response.prototype.field;
/**
* @type {!Array.<T>}
*/
registry.json.Response.prototype.results;
// XXX: Might not need undefineds here.
/**
* @typedef {{
* allowedTlds: !Array<string>,
* clientIdentifier: string,
* clientCertificate: string?,
* clientCertificateHash: string?,
* failoverClientCertificate: string?,
* failoverClientCertificateHash: string?,
* driveFolderId: string?,
* ianaIdentifier: (number?|undefined),
* icannReferralEmail: string,
* ipAddressWhitelist: !Array<string>,
* emailAddress: (string?|undefined),
* lastUpdateTime: string,
* url: (string?|undefined),
* phonePasscode: (string?|undefined),
* phoneNumber: (string?|undefined),
* faxNumber: (string?|undefined),
* localizedAddress: registry.json.RegistrarAddress,
* whoisServer: (string?|undefined),
* referralUrl: (string?|undefined),
* contacts: !Array.<registry.json.RegistrarContact>
* }}
*/
registry.json.Registrar;
/**
* @typedef {{
* street: !Array.<string>,
* city: string,
* state: (string?|undefined),
* zip: (string?|undefined),
* countryCode: string
* }}
*/
registry.json.RegistrarAddress;
/**
* @typedef {{
* name: (string?|undefined),
* emailAddress: string,
* visibleInWhoisAsAdmin: boolean,
* visibleInWhoisAsTech: boolean,
* visibleInDomainWhoisAsAbuse: boolean,
* phoneNumber: (string?|undefined),
* faxNumber: (string?|undefined),
* types: (string?|undefined)
* }}
*/
registry.json.RegistrarContact;

View file

@ -0,0 +1,79 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Component');
goog.forwardDeclare('registry.Console');
goog.require('goog.events.EventHandler');
goog.require('registry.util');
/**
* Base component for UI.
*
* <pre>
* ui/js/component.js - Base UI class.
* ^
* |
* edit_item.js - Common controls for editable items.
* ^
* \
* |-ui/js/resource_component.js - JSON resources
* ^
* \
* |- ui/js/registrar/settings.js
* </pre>
*
* @param {!registry.Console} cons the console singleton.
* @constructor
* @extends {goog.events.EventHandler}
*/
registry.Component = function(cons) {
registry.Component.base(this, 'constructor');
/** @type {!registry.Console} */
this.console = cons;
/**
* The hashPath this component is mapped by. This is set by the
* console after construction.
* @type {string}
*/
this.basePath = '';
/**
* Bean counter that's used by `addRemBtnHandlers`,
* e.g. `typeCounts['host']++` when user adds or removes.
* @type {!Object.<string, number>}
* @protected
*/
this.typeCounts = {};
/**
* Stateful UI/server session model.
* @type {?Object.<string, ?>}
*/
this.model = null;
};
goog.inherits(registry.Component, goog.events.EventHandler);
/**
* Subclasses should override this to implement panel display.
* @param {string} id The target resource id.
*/
registry.Component.prototype.bindToDom = function(id) {
registry.util.unbutter();
};

View file

@ -0,0 +1,144 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Console');
goog.require('goog.Disposable');
goog.require('goog.History');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.history.EventType');
goog.require('registry.util');
goog.forwardDeclare('goog.events.KeyEvent');
/**
* Abstract console for both admin and registrar console UIs.
* @constructor
* @extends {goog.Disposable}
*/
registry.Console = function() {
registry.Console.base(this, 'constructor');
/**
* @type {!goog.History}
* @protected
*/
this.history = new goog.History();
};
goog.inherits(registry.Console, goog.Disposable);
/**
* Registers the console's events and sets everything up.
*
* Should be called after the constructor.
*
* The reason this isn't done in the constructor is that this is a base class
* designed to be extended. We have to wait for the actual implementation to
* finish constructing before using it.
*/
registry.Console.prototype.setUp = function() {
goog.events.listen(
this.history,
goog.history.EventType.NAVIGATE,
goog.bind(this.handleHashChange, this));
this.bindToDom();
// goog.History always starts off as "not enabled", meaning it doesn't trigger
// the listeners on change.
//
// When it's set to be enabled, it will start triggering the listeners on
// every change, but it also triggers the listeners immediately with the
// current history entry.
//
// This means the handleHashChange listener registered above will be called
// now.
this.history.setEnabled(true);
};
/**
* Helper to setup permanent page elements.
*/
registry.Console.prototype.bindToDom = function() {
registry.util.unbutter();
goog.events.listen(goog.dom.getRequiredElement('kd-searchbutton'),
goog.events.EventType.CLICK,
goog.bind(this.onSearch_, this));
goog.events.listen(goog.dom.getRequiredElement('kd-searchfield'),
goog.events.EventType.KEYUP,
goog.bind(this.onSearchFieldKeyUp_, this));
goog.events.listen(
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-dismiss')),
goog.events.EventType.CLICK,
registry.util.unbutter);
};
/**
* Subclasses should override to visit the hash token given by
* `goog.History.getToken()`.
*/
registry.Console.prototype.handleHashChange = goog.abstractMethod;
/**
* @param {string} resourcePath Resource description path.
*/
registry.Console.prototype.view = function(resourcePath) {
// Setting the new history token will also trigger the handleHashChange
// listener registered in the setUp() function.
this.history.setToken(resourcePath);
};
/**
* Handler for search bar.
* @private
*/
registry.Console.prototype.onSearch_ = function() {
var qElt = goog.dom.getRequiredElement('kd-searchfield');
if (qElt.getAttribute('disabled')) {
return;
}
var query = qElt.value;
if (query == '') {
return;
}
// Filtering this value change event.
qElt.setAttribute('disabled', true);
qElt.value = '';
this.view(query);
qElt.removeAttribute('disabled');
};
/**
* Handler for key press in the search input field.
* @param {!goog.events.KeyEvent} e Key event to handle.
* @return {boolean} Whether the event should be continued or cancelled.
* @private
*/
registry.Console.prototype.onSearchFieldKeyUp_ = function(e) {
if (e.keyCode == goog.events.KeyCodes.ENTER) {
this.onSearch_();
return false;
}
return true;
};

View file

@ -0,0 +1,315 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.EditItem');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('registry.Component');
goog.require('registry.soy.console');
goog.require('registry.util');
goog.forwardDeclare('registry.Console');
/**
* An editable item, with Edit and Save/Cancel buttons in the appbar.
* @param {!registry.Console} cons
* @param {function()} itemTmpl
* @param {boolean} isEditable
* @constructor
* @extends {registry.Component}
*/
registry.EditItem = function(cons, itemTmpl, isEditable) {
registry.EditItem.base(this, 'constructor', cons);
/**
* @type {!Function}
*/
this.itemTmpl = itemTmpl;
/**
* Optional current target resource id.
* @type {?string}
*/
this.id = null;
/**
* Should the "edit" button be enabled?
* @type {boolean}
*/
this.isEditable = isEditable;
/**
* Transitional id for next resource during create.
* @type {?string}
*/
this.nextId = null;
};
goog.inherits(registry.EditItem, registry.Component);
/** @override */
registry.EditItem.prototype.bindToDom = function(id) {
registry.EditItem.base(this, 'bindToDom', id);
this.id = id;
this.nextId = null;
this.setupAppbar();
};
/** Setup appbar save/edit buttons. */
registry.EditItem.prototype.setupAppbar = function() {
goog.soy.renderElement(goog.dom.getRequiredElement('reg-app-buttons'),
registry.soy.console.appbarButtons);
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-add'),
goog.events.EventType.CLICK,
goog.bind(this.add, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-edit'),
goog.events.EventType.CLICK,
goog.bind(this.edit, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-save'),
goog.events.EventType.CLICK,
goog.bind(this.save, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-cancel'),
goog.events.EventType.CLICK,
goog.bind(this.cancel, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-back'),
goog.events.EventType.CLICK,
goog.bind(this.back, this));
// Show the add/edit buttons only if isEditable.
// "edit" is shown if we have an item's ID
// "add" is shown if we don't have an item's ID
registry.util.setVisible('reg-app-btns-edit', this.isEditable && !!this.id);
registry.util.setVisible('reg-app-btn-add', this.isEditable && !this.id);
};
/**
* Retrieve item from server. Overrides should callback to
* `#handleFetchItem(string, !Object)`.
* @param {string} id item id.
*/
registry.EditItem.prototype.fetchItem = goog.abstractMethod;
/**
* Handle result decoding and display.
* @param {string} id The requested ID/name, for error message.
* @param {!Object} rsp The requested object.
*/
registry.EditItem.prototype.handleFetchItem = goog.abstractMethod;
/** Subclasses should override to continue processing after fetch. */
registry.EditItem.prototype.processItem = function() {};
/**
* Show the item.
* @param {!Object} objArgs
*/
registry.EditItem.prototype.renderItem = function(objArgs) {
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
this.itemTmpl,
objArgs);
this.runAfterRender(objArgs);
};
/**
* @return {boolean} if the component is currently being edited.
*/
registry.EditItem.prototype.isEditing = function() {
return goog.dom.classlist.contains(
goog.dom.getElement('reg-app'), goog.getCssName('editing'));
};
/**
* Toggles the editing state of a component. This will first hide the
* elements of the `shown` class, then adds the `editing`
* style to the component, then shows the elements that had the `hidden`
* class.
*/
registry.EditItem.prototype.toggleEdit = function() {
// Toggle appbar buttons.
var addBtn = goog.dom.getRequiredElement('reg-app-btn-add');
var editBtns = goog.dom.getRequiredElement('reg-app-btns-edit');
var saveBtns = goog.dom.getRequiredElement('reg-app-btns-save');
var editing = goog.dom.classlist.contains(saveBtns,
registry.util.cssShown);
if (editing) {
registry.util.setVisible(saveBtns, false);
if (this.id) {
registry.util.setVisible(editBtns, true);
} else {
registry.util.setVisible(addBtn, true);
}
} else {
if (this.id) {
registry.util.setVisible(editBtns, false);
} else {
registry.util.setVisible(addBtn, false);
}
registry.util.setVisible(saveBtns, true);
}
// Then page contents.
var parentElt = goog.dom.getElement('reg-content');
var shownCssName = goog.getCssName('shown');
var hiddenCssName = goog.getCssName('hidden');
var shown = goog.dom.getElementsByClass(shownCssName, parentElt);
var hidden = goog.dom.getElementsByClass(hiddenCssName, parentElt);
for (var i = 0; i < shown.length; i++) {
goog.dom.classlist.addRemove(shown[i], shownCssName, hiddenCssName);
}
// Then add editing styles.
var editingCssName = goog.getCssName('editing');
var startingEdit = !goog.dom.classlist.contains(parentElt, editingCssName);
if (startingEdit) {
goog.dom.classlist.remove(parentElt, editingCssName);
} else {
goog.dom.classlist.add(parentElt, editingCssName);
}
// The show hiddens.
for (var i = 0; i < hidden.length; i++) {
goog.dom.classlist.addRemove(hidden[i], hiddenCssName, shownCssName);
}
};
/**
* Subclasses should override to enhance the default model.
* @return {!Object.<string, ?>}
*/
registry.EditItem.prototype.newModel = function() {
return {item: {}};
};
// N.B. setting these as abstract precludes their correct binding in
// setupAppbar.
/** Show add item panel. */
registry.EditItem.prototype.add = function() {};
/** Go back from item to collection view. */
registry.EditItem.prototype.back = function() {};
/** Sets up initial edit model state and then called edit(objArgs). */
registry.EditItem.prototype.edit = function() {
var objArgs = this.model;
if (objArgs == null) {
objArgs = this.newModel();
}
// XXX: This is vestigial. In the new msg format, this pollutes the
// server-model state. Should be carried elsewhere.
objArgs.readonly = false;
this.renderItem(objArgs);
this.setupEditor(objArgs);
this.toggleEdit();
};
/**
* Save the current item. Calls either create if creating a new
* object or update otherwise.
*/
registry.EditItem.prototype.save = function() {
if (this.model == null) {
this.sendCreate();
} else {
this.sendUpdate();
}
};
/**
* Resets to non-editing, readonly state, or visits home screen if the
* page was in on a create panel.
*/
registry.EditItem.prototype.cancel = function() {
this.toggleEdit();
// XXX: The presence of a model is sufficient for non-collection pages, but an
// empty id also means go to the collection in collection pages. Should
// be simplified.
if (this.model && this.id != '') {
this.model.readonly = true;
this.renderItem(this.model);
} else {
this.bindToDom('');
}
};
/**
* Called after this.renderItem(), to allow for further setup of
* editing.
*
* TODO(b/122661518): merge this with runAfterRender so we don't have two
* similar methods
* @param {!Object} objArgs
*/
registry.EditItem.prototype.setupEditor = function(objArgs) {};
/**
* Called at the end of this.renderItem() to allow for registration
* of listeners and other tasks that depend on the existence of the items in the
* DOM
* @param {!Object} objArgs
*/
registry.EditItem.prototype.runAfterRender = function(objArgs) {};
// XXX: These should really take @param {object} which is the form. Alas the
// only override which doesn't work this way, ResourceComponent.sendCreate
// breaks this opportunity. Hmmm...
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendCreate = goog.abstractMethod;
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendUpdate = goog.abstractMethod;
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendDelete = goog.abstractMethod;
/**
* Subclasses should override to populate update queryParams with form
* fields as needed. `queryParams.nextId` MUST be set to the
* new object's ID.
* @param {!Object} queryParams
*/
registry.EditItem.prototype.prepareUpdate = goog.abstractMethod;
/**
* Subclasses should provide a function to parse JSON response from server and
* return a result object as described below.
* @param {!Object} rsp Decoded JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.EditItem.prototype.handleUpdateResponse = goog.abstractMethod;

View file

@ -0,0 +1,137 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.forms');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.dom.forms');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('registry.util');
goog.forwardDeclare('goog.events.KeyEvent');
/**
* Sets the focus on a form field (if it exists).
* @param {Element|string} field Form field (or ID) to focus.
*/
registry.forms.focus = function(field) {
field = goog.dom.getElement(field);
if (!goog.isNull(field) && goog.dom.isFocusable(field)) {
goog.dom.forms.focusAndSelect(field);
}
};
/**
* Displays a form field error, or butters if no field is specified.
* @param {string} message Human-readable explanation of why this field is evil.
* @param {string=} opt_field Erroneous field name.
*/
registry.forms.displayError = function(message, opt_field) {
if (!goog.isDef(opt_field)) {
registry.util.butter(message);
return;
}
var input = goog.dom.getElement(opt_field) ||
goog.dom.getElement(opt_field + '[0]');
// XXX: Transitioning to use of form.eltId instead of DOM id. If DOM id
// lookup fails, then search forms for the named field.
if (goog.isDefAndNotNull(opt_field) && goog.isNull(input)) {
for (var fNdx in document.forms) {
var form = document.forms[fNdx];
if (form[opt_field]) {
input = form[opt_field];
break;
}
}
}
if (!goog.isNull(input)) {
goog.dom.classlist.add(input, goog.getCssName('kd-formerror'));
goog.dom.insertSiblingAfter(
goog.dom.createDom(goog.dom.TagName.DIV,
goog.getCssName('kd-errormessage'),
message),
input);
registry.forms.focus(input);
} else {
registry.util.butter(opt_field + ': ' + message);
}
};
/** Removes error markup from whois settings form. */
registry.forms.resetErrors = function() {
registry.util.unbutter();
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-formerror')),
function(field) {
goog.dom.classlist.remove(field, goog.getCssName('kd-formerror'));
});
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-errormessage')),
goog.dom.removeNode);
};
/**
* Adds enter key listeners to all form fields.
* @param {!Element} container Parent element containing INPUT fields.
* @param {function()} callback Called when enter is pressed in a field.
*/
registry.forms.listenFieldsOnEnter = function(container, callback) {
var handler = goog.partial(registry.forms.onFieldKeyUp_, callback);
goog.array.forEach(
goog.dom.getElementsByTagNameAndClass(
goog.dom.TagName.INPUT, undefined, container),
function(field) {
goog.events.listen(field, goog.events.EventType.KEYUP, handler);
});
};
/**
* Event handler that saves form when enter is pressed in a form field.
* @param {function()} callback Called when key pressed is ENTER.
* @param {!goog.events.KeyEvent} e Key event to handle.
* @return {boolean} Whether the event should be continued or cancelled.
* @private
*/
registry.forms.onFieldKeyUp_ = function(callback, e) {
if (e.keyCode == goog.events.KeyCodes.ENTER) {
callback();
return false;
}
return true;
};
/**
* Toggles disabled state of a button or form element.
* @param {!Element} element Form element to disable.
* @param {boolean} enabled Enables element if true, or else disables it.
*/
registry.forms.setEnabled = function(element, enabled) {
if (enabled) {
goog.dom.classlist.remove(element, goog.getCssName('disabled'));
} else {
goog.dom.classlist.add(element, goog.getCssName('disabled'));
element.blur();
}
};

View file

@ -0,0 +1,137 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.MenuButton');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.forwardDeclare('goog.events.BrowserEvent');
/**
* Kennedy style menu button.
* @param {!Element} button Menu button element.
* @constructor
* @extends {goog.events.EventHandler}
* @final
*/
registry.MenuButton = function(button) {
registry.MenuButton.base(this, 'constructor');
/**
* Outer menu button element.
* @private {!Element}
* @const
*/
this.button_ = button;
this.listen(button, goog.events.EventType.CLICK, this.onButtonClick_);
/**
* Label that displays currently selected item.
* @private {!Element}
* @const
*/
this.label_ =
goog.dom.getRequiredElementByClass(goog.getCssName('label'), button);
/**
* List of selectable items.
* @private {!Element}
* @const
*/
this.menu_ =
goog.dom.getRequiredElementByClass(goog.getCssName('kd-menulist'),
button);
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'), button),
function(item) {
this.listen(item, goog.events.EventType.CLICK, this.onItemClick_);
},
this);
};
goog.inherits(registry.MenuButton, goog.events.EventHandler);
/**
* Returns selected value in menu.
* @return {string}
*/
registry.MenuButton.prototype.getValue = function() {
return goog.dom.getTextContent(
goog.dom.getRequiredElementByClass(
goog.getCssName('selected'),
this.button_));
};
/**
* Main menu button handler.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.MenuButton.prototype.onButtonClick_ = function(e) {
if (goog.dom.classlist.contains(this.button_, goog.getCssName('selected'))) {
return;
}
e.stopPropagation();
goog.dom.classlist.add(this.button_, goog.getCssName('selected'));
goog.dom.classlist.add(this.menu_, goog.getCssName('shown'));
this.listenOnce(goog.dom.getDocument().body, goog.events.EventType.CLICK,
this.hideMenu_);
};
/**
* Menu item selection handler.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.MenuButton.prototype.onItemClick_ = function(e) {
e.stopPropagation();
if (goog.dom.classlist.contains(this.button_, goog.getCssName('disabled'))) {
return;
}
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'),
this.button_),
function(item) {
goog.dom.classlist.remove(item, goog.getCssName('selected'));
},
this);
goog.asserts.assert(e.target instanceof Element);
goog.dom.classlist.add(e.target, goog.getCssName('selected'));
var text = goog.dom.getTextContent(e.target);
goog.dom.setTextContent(this.label_, text);
goog.events.fireListeners(this.button_, goog.events.EventType.CHANGE,
false, {newValue: text});
this.hideMenu_();
};
/**
* Hide the menu.
* @private
*/
registry.MenuButton.prototype.hideMenu_ = function() {
goog.dom.classlist.remove(this.menu_, goog.getCssName('shown'));
goog.dom.classlist.remove(this.button_, goog.getCssName('selected'));
};

View file

@ -0,0 +1,152 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.AdminSettings');
goog.forwardDeclare('registry.registrar.Console');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.net.XhrIo');
goog.require('goog.soy');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.admin');
/**
* Admin Settings page, such as allowed TLDs for this registrar.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.AdminSettings = function(console, resource) {
registry.registrar.AdminSettings.base(
this, 'constructor', console, resource,
registry.soy.registrar.admin.settings, console.params.isAdmin, null);
};
goog.inherits(registry.registrar.AdminSettings, registry.ResourceComponent);
/** @override */
registry.registrar.AdminSettings.prototype.bindToDom = function(id) {
registry.registrar.AdminSettings.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};
/** @override */
registry.registrar.AdminSettings.prototype.runAfterRender = function(objArgs) {
const oteButton = goog.dom.getElement('btn-ote-status');
if (oteButton) {
goog.events.listen(
oteButton,
goog.events.EventType.CLICK,
goog.bind(
this.oteStatusCheck_, this, objArgs.xsrfToken, objArgs.clientId),
false, this);
}
};
/** @override */
registry.registrar.AdminSettings.prototype.setupEditor = function(objArgs) {
goog.dom.classlist.add(
goog.dom.getRequiredElement('tlds'), goog.getCssName('editing'));
var tlds = goog.dom.getElementsByClass(
goog.getCssName('tld'), goog.dom.getRequiredElement('tlds'));
goog.array.forEach(tlds, function(tld) {
var remBtn = goog.dom.getChildren(tld)[0];
goog.events.listen(
remBtn, goog.events.EventType.CLICK,
goog.bind(this.onTldRemove_, this, remBtn));
}, this);
this.typeCounts['reg-tlds'] =
objArgs.allowedTlds ? objArgs.allowedTlds.length : 0;
goog.events.listen(
goog.dom.getRequiredElement('btn-add-tld'), goog.events.EventType.CLICK,
this.onTldAdd_, false, this);
};
/**
* JSON response prefix which prevents evaluation.
* @private {string}
* @const
*/
registry.registrar.AdminSettings.PARSER_BREAKER_ = ')]}\'\n';
/**
* Click handler for OT&E status checking button.
* @param {string} xsrfToken
* @param {string} clientId
* @private
*/
registry.registrar.AdminSettings.prototype.oteStatusCheck_ = function(
xsrfToken, clientId) {
goog.net.XhrIo.send('/registrar-ote-status', function(e) {
var response =
/** @type {!registry.json.ote.OteStatusResponse} */
(e.target.getResponseJson(
registry.registrar.AdminSettings.PARSER_BREAKER_));
var oteResultParent = goog.dom.getRequiredElement('ote-status-area-parent');
if (response.status === 'SUCCESS') {
var results = response.results[0];
goog.soy.renderElement(
oteResultParent, registry.soy.registrar.admin.oteResultsTable,
{completed: results.completed, detailsList: results.details});
} else {
goog.soy.renderElement(
oteResultParent, registry.soy.registrar.admin.oteErrorArea,
{message: response.message});
}
}, 'POST', goog.json.serialize({'clientId': clientId}), {
'X-CSRF-Token': xsrfToken,
'Content-Type': 'application/json; charset=UTF-8'
});
};
/**
* Click handler for TLD add button.
* @private
*/
registry.registrar.AdminSettings.prototype.onTldAdd_ = function() {
const tldInputElt = goog.dom.getRequiredElement('newTld');
const tldElt = goog.soy.renderAsFragment(registry.soy.registrar.admin.tld, {
name: 'allowedTlds[' + this.typeCounts['reg-tlds'] + ']',
tld: tldInputElt.value,
});
goog.dom.appendChild(goog.dom.getRequiredElement('tlds'), tldElt);
var remBtn = goog.dom.getFirstElementChild(tldElt);
goog.dom.classlist.remove(remBtn, goog.getCssName('hidden'));
goog.events.listen(
remBtn, goog.events.EventType.CLICK,
goog.bind(this.onTldRemove_, this, remBtn));
this.typeCounts['reg-tlds']++;
tldInputElt.value = '';
};
/**
* Click handler for TLD remove button.
* @param {!Element} remBtn The remove button.
* @private
*/
registry.registrar.AdminSettings.prototype.onTldRemove_ = function(remBtn) {
goog.dom.removeNode(goog.dom.getParentElement(remBtn));
};

View file

@ -0,0 +1,170 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Console');
goog.require('goog.Uri');
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.net.XhrIo');
goog.require('registry.Console');
goog.require('registry.Resource');
goog.require('registry.registrar.AdminSettings');
goog.require('registry.registrar.ContactSettings');
goog.require('registry.registrar.ContactUs');
goog.require('registry.registrar.Dashboard');
goog.require('registry.registrar.Resources');
goog.require('registry.registrar.SecuritySettings');
goog.require('registry.registrar.WhoisSettings');
goog.require('registry.util');
goog.forwardDeclare('registry.Component');
/**
* The Registrar Console.
* @param {!Object} params Parameters to be passed into templates. These are
* a combination of configurable parameters (e.g. phone number) and
* user/session/registrar specific parameters. See
* registrar/Console.soy#.main for expected contents.
* @constructor
* @extends {registry.Console}
* @final
*/
registry.registrar.Console = function(params) {
registry.registrar.Console.base(this, 'constructor');
/**
* @type {!Object}
*/
this.params = params;
/**
* Component that's currently embedded in the page.
* @type {?registry.Component}
* @private
*/
this.component_ = null;
/**
* Last active nav element.
* @type {?Element}
*/
this.lastActiveNavElt;
/**
* A map from the URL fragment to the component to show.
*
* @type {!Object.<string, function(new:registry.Component,
* !registry.registrar.Console,
* !registry.Resource)>}
*/
this.pageMap = {};
// Homepage. Displayed when there's no fragment, or when the fragment doesn't
// correspond to any view
this.pageMap[''] = registry.registrar.Dashboard;
// Updating the Registrar settings
this.pageMap['security-settings'] = registry.registrar.SecuritySettings;
this.pageMap['contact-settings'] = registry.registrar.ContactSettings;
this.pageMap['whois-settings'] = registry.registrar.WhoisSettings;
this.pageMap['contact-us'] = registry.registrar.ContactUs;
this.pageMap['resources'] = registry.registrar.Resources;
// For admin use. The relevant tab is only shown in Console.soy for admins,
// but we also need to remove it here, otherwise it'd still be accessible if
// the user manually puts '#admin-settings' in the URL.
//
// Both the Console.soy and here, the "hiding the admin console for non
// admins" is purely for "aesthetic / design" reasons and have NO security
// implications.
//
// The security implications are only in the backend where we make sure all
// changes are made by users with the correct access (in other words - we
// don't trust the client-side to secure our application anyway)
if (this.params.isAdmin) {
this.pageMap['admin-settings'] = registry.registrar.AdminSettings;
}
};
goog.inherits(registry.registrar.Console, registry.Console);
/**
* Changes the content area depending on hash path.
*
* <p>Hash path is expected to be of the form:
*
* <pre>
* #type/id
* </pre>
*
* <p>The `id` part may be appended by `()` to specify that the target
* should be a resource create page.
*
* @override
*/
registry.registrar.Console.prototype.handleHashChange = function() {
var hashToken = this.history.getToken();
var parts = hashToken.split('/');
var type = '';
var id = '';
if (parts.length >= 1) {
type = parts[0];
}
if (parts.length == 2) {
id = parts[1];
}
goog.net.XhrIo.cleanup();
var componentCtor = this.pageMap[type];
if (componentCtor == undefined) {
componentCtor = this.pageMap[''];
}
var oldComponent = this.component_;
const resource = new registry.Resource(
new goog.Uri('/registrar-settings'), this.params.clientId,
this.params.xsrfToken);
this.component_ = new componentCtor(this, resource);
this.registerDisposable(this.component_);
this.component_.basePath = type;
this.component_.bindToDom(id);
this.changeNavStyle();
goog.dispose(oldComponent);
};
/** Change nav style. */
registry.registrar.Console.prototype.changeNavStyle = function() {
var hashToken = this.history.getToken();
// Path except id
var slashNdx = hashToken.lastIndexOf('/');
slashNdx = slashNdx == -1 ? hashToken.length : slashNdx;
var regNavlist = goog.dom.getRequiredElement('reg-navlist');
var path = hashToken.substring(0, slashNdx);
var active = regNavlist.querySelector('a[href="#' + path + '"]');
if (goog.isNull(active)) {
registry.util.log('Unknown path or path form in changeNavStyle.');
return;
}
if (this.lastActiveNavElt) {
goog.dom.classlist.remove(
this.lastActiveNavElt, goog.getCssName('domain-active-nav'));
}
goog.dom.classlist.add(active, goog.getCssName('domain-active-nav'));
this.lastActiveNavElt = active;
};

View file

@ -0,0 +1,252 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.ContactSettings');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.soy');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.contacts');
goog.require('registry.util');
goog.forwardDeclare('registry.registrar.Console');
/**
* Contact Settings page. Registrar Contacts are not really a proper
* REST resource as they're still passed from the server as a member
* field of Registrar, so this class behaves like an item page,
* updating only that field of the Registrar object and not
* implementing the create action from edit_item.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.ContactSettings = function(console, resource) {
registry.registrar.ContactSettings.base(
this, 'constructor', console, resource,
registry.soy.registrar.contacts.contact, console.params.isOwner, null);
};
goog.inherits(registry.registrar.ContactSettings, registry.ResourceComponent);
/** @override */
registry.registrar.ContactSettings.prototype.setupAppbar = function() {
registry.registrar.ContactSettings.base(this, 'setupAppbar');
// Setup delete only on existing items, not on creates.
if (goog.isDefAndNotNull(this.model)) {
var deleteBtn = goog.dom.createDom(
goog.dom.TagName.BUTTON, {
type: 'button',
id: 'reg-app-btn-delete',
className: goog.getCssName('kd-button')
},
'Delete');
goog.events.listen(deleteBtn, goog.events.EventType.CLICK,
goog.bind(this.sendDelete, this));
goog.dom.insertSiblingBefore(deleteBtn,
goog.dom.getRequiredElement('reg-app-btn-cancel'));
}
};
/** @override */
registry.registrar.ContactSettings.prototype.renderItem = function(rspObj) {
var contentElt = goog.dom.getRequiredElement('reg-content');
/** @type {!registry.json.RegistrarContact} */
var contacts = rspObj.contacts;
if (this.id) {
var targetContactNdx;
var targetContact;
for (var c in contacts) {
var ct = contacts[c];
if (ct.emailAddress == this.id) {
targetContactNdx = c;
targetContact = ct;
break;
}
}
if (!targetContact) {
registry.util.butter('No contact with the given email.');
return;
}
var typesList = targetContact.types.toLowerCase().split(',');
var actualTypesLookup = {};
for (var t in typesList) {
actualTypesLookup[typesList[t]] = true;
}
goog.soy.renderElement(
contentElt,
registry.soy.registrar.contacts.contact,
{
item: targetContact,
namePrefix: 'contacts[' + targetContactNdx + '].',
actualTypesLookup: actualTypesLookup,
readonly: (rspObj.readonly || false)
});
this.setupAppbar();
} else {
var contactsByType = {};
for (var c in contacts) {
var contact = contacts[c];
var types = contact.types;
if (!types) {
// If the contact has no types, synthesize an "OTHER" type so that it
// still will be displayed in the console.
types = 'OTHER';
}
types = types.split(',');
for (var t in types) {
var type = types[t].toLowerCase();
var contactsList = contactsByType[type];
if (!contactsList) {
contactsByType[type] = contactsList = [];
}
contactsList.push(contact);
}
}
goog.soy.renderElement(
contentElt,
registry.soy.registrar.contacts.set,
{contactsByType: contactsByType });
}
};
/** @override */
registry.registrar.ContactSettings.prototype.add = function() {
var newContactNdx = this.model.contacts.length;
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
registry.soy.registrar.contacts.contact,
{
item: {},
namePrefix: 'contacts[' + newContactNdx + '].',
actualTypesLookup: {},
readonly: false
});
this.toggleEdit();
};
/** @override */
registry.registrar.ContactSettings.prototype.sendDelete = function() {
var ndxToDel = null;
for (var i = 0; i < this.model.contacts.length; i++) {
var contact = this.model.contacts[i];
if (contact.emailAddress == this.id) {
ndxToDel = i;
}
}
if (goog.isNull(ndxToDel)) {
throw new Error('Email to delete does not match model.');
}
var modelCopy = /** @type {!Object}
*/ (JSON.parse(goog.json.serialize(this.model)));
goog.array.removeAt(modelCopy.contacts, ndxToDel);
this.resource.update(modelCopy, goog.bind(this.handleDeleteResponse, this));
};
/** @override */
registry.registrar.ContactSettings.prototype.prepareUpdate =
function(modelCopy) {
var form = registry.util.parseForm('item');
var contact;
// Handle update/create.
if (this.id) {
// Update contact, so overwrite it in the model before sending
// back to server.
var once = false;
for (var c in form.contacts) {
if (once) {
throw new Error('More than one contact parsed from form: ' + c);
}
contact = form.contacts[c];
modelCopy.contacts[c] = contact;
once = true;
}
} else {
// Add contact.
contact = form.contacts.pop();
modelCopy.contacts.push(contact);
}
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
contact.visibleInDomainWhoisAsAbuse =
contact.visibleInDomainWhoisAsAbuse == 'true';
contact.types = '';
for (var tNdx in contact.type) {
if (contact.type[tNdx]) {
if (contact.types.length > 0) {
contact.types += ',';
}
contact.types += ('' + tNdx).toUpperCase();
}
}
delete contact['type'];
// Override previous domain WHOIS abuse contact.
if (contact.visibleInDomainWhoisAsAbuse) {
for (var c in modelCopy.contacts) {
if (modelCopy.contacts[c].emailAddress != contact.emailAddress) {
modelCopy.contacts[c].visibleInDomainWhoisAsAbuse = false;
}
}
}
this.nextId = contact.emailAddress;
};
// XXX: Should be hoisted up.
/**
* Handler for contact save that navigates to that item on success.
* Does nothing on failure as UI will be left with error messages for
* the user to resolve.
* @param {!Object} rsp Decoded JSON response from the server.
* @override
*/
registry.registrar.ContactSettings.prototype.handleCreateResponse =
function(rsp) {
this.handleUpdateResponse(rsp);
if (rsp.status == 'SUCCESS') {
this.console.view('contact-settings/' + this.nextId);
}
return rsp;
};
/**
* Handler for contact delete that navigates back to the collection on success.
* Does nothing on failure as UI will be left with error messages for
* the user to resolve.
* @param {!Object} rsp Decoded JSON response from the server.
* @override
*/
registry.registrar.ContactSettings.prototype.handleDeleteResponse =
function(rsp) {
this.handleUpdateResponse(rsp);
if (rsp.status == 'SUCCESS') {
this.id = null;
this.console.view('contact-settings');
}
return rsp;
};

View file

@ -0,0 +1,46 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.ContactUs');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.console');
goog.forwardDeclare('registry.registrar.Console');
/**
* Contact Us page.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.ContactUs = function(console, resource) {
registry.registrar.ContactUs.base(
this, 'constructor', console, resource,
registry.soy.registrar.console.contactUs, console.params.isOwner, null);
};
goog.inherits(registry.registrar.ContactUs, registry.ResourceComponent);
/** @override */
registry.registrar.ContactUs.prototype.bindToDom = function(id) {
registry.registrar.ContactUs.base(this, 'bindToDom', '');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
};

View file

@ -0,0 +1,89 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Dashboard');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('goog.style');
goog.require('registry.Component');
goog.require('registry.soy.registrar.console');
goog.forwardDeclare('registry.registrar.Console');
/**
* Dashboard for Registrar Console.
* @param {!registry.registrar.Console} console
* @constructor
* @extends {registry.Component}
* @final
*/
registry.registrar.Dashboard = function(console) {
registry.registrar.Dashboard.base(this, 'constructor', console);
/** @private {number} */
this.x_ = 0;
/** @private {?Element} */
this.gear_ = null;
/** @private {?goog.Timer} */
this.timer_ = null;
};
goog.inherits(registry.registrar.Dashboard, registry.Component);
/** @override */
registry.registrar.Dashboard.prototype.bindToDom = function(id) {
registry.registrar.Dashboard.base(this, 'bindToDom', '');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
goog.soy.renderElement(goog.dom.getElement('reg-content'),
registry.soy.registrar.console.dashboard,
this.console.params);
goog.events.listen(goog.dom.getElement('rotate'),
goog.events.EventType.CLICK,
goog.bind(this.rotate_, this));
this.gear_ = goog.dom.getRequiredElement('gear');
};
/**
* Let's do the twist.
* @private
*/
registry.registrar.Dashboard.prototype.rotate_ = function() {
this.timer_ = new goog.Timer(10);
this.registerDisposable(this.timer_);
goog.events.listen(this.timer_, goog.Timer.TICK,
goog.bind(this.rotateCall_, this));
this.timer_.start();
};
/**
* No really this time!
* @private
*/
registry.registrar.Dashboard.prototype.rotateCall_ = function() {
this.x_++;
goog.style.setStyle(this.gear_, 'transform', 'rotate(' + this.x_ + 'deg)');
if (this.x_ == 360) {
this.timer_.stop();
}
};

View file

@ -0,0 +1,61 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Entry point for the registrar console.
*/
goog.provide('registry.registrar.main');
goog.require('registry.registrar.Console');
/**
* Instantiates a registry object, which syncs with the server.
* Sub-templates are invoked based on location/pathname, to choose
* either Registry or Registrar pages.
*
* @param {string} xsrfToken populated by server-side soy template.
* @param {string} clientId The registrar clientId.
* @param {boolean} isAdmin
* @param {boolean} isOwner
* @param {string} productName the product name displayed by the UI.
* @param {string} integrationEmail
* @param {string} supportEmail
* @param {string} announcementsEmail
* @param {string} supportPhoneNumber
* @param {string} technicalDocsUrl
* @param {string} environment
* @export
*/
registry.registrar.main = function(
xsrfToken, clientId, isAdmin, isOwner, productName, integrationEmail,
supportEmail, announcementsEmail, supportPhoneNumber, technicalDocsUrl,
environment) {
const console = new registry.registrar.Console({
xsrfToken: xsrfToken,
clientId: clientId,
isAdmin: isAdmin,
isOwner: isOwner,
productName: productName,
integrationEmail: integrationEmail,
supportEmail: supportEmail,
announcementsEmail: announcementsEmail,
supportPhoneNumber: supportPhoneNumber,
technicalDocsUrl: technicalDocsUrl,
environment: environment,
});
console.setUp();
};

View file

@ -0,0 +1,47 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Resources');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.console');
goog.forwardDeclare('registry.registrar.Console');
/**
* Resources and billing page.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.Resources = function(console, resource) {
registry.registrar.Resources.base(
this, 'constructor', console, resource,
registry.soy.registrar.console.resources, console.params.isOwner, null);
};
goog.inherits(registry.registrar.Resources,
registry.ResourceComponent);
/** @override */
registry.registrar.Resources.prototype.bindToDom = function(id) {
registry.registrar.Resources.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};

View file

@ -0,0 +1,106 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.SecuritySettings');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.security');
goog.forwardDeclare('registry.registrar.Console');
/**
* Security Settings page.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.SecuritySettings = function(console, resource) {
registry.registrar.SecuritySettings.base(
this, 'constructor', console, resource,
registry.soy.registrar.security.settings, console.params.isOwner, null);
};
goog.inherits(registry.registrar.SecuritySettings, registry.ResourceComponent);
/** @override */
registry.registrar.SecuritySettings.prototype.bindToDom = function(id) {
registry.registrar.SecuritySettings.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};
/** @override */
registry.registrar.SecuritySettings.prototype.setupEditor =
function(objArgs) {
goog.dom.classlist.add(goog.dom.getRequiredElement('ips'),
goog.getCssName('editing'));
var ips = goog.dom.getElementsByClass(goog.getCssName('ip'),
goog.dom.getRequiredElement('ips'));
goog.array.forEach(ips, function(ip) {
var remBtn = goog.dom.getChildren(ip)[0];
goog.events.listen(remBtn,
goog.events.EventType.CLICK,
goog.bind(this.onIpRemove_, this, remBtn));
}, this);
this.typeCounts['reg-ips'] = objArgs.ipAddressWhitelist ?
objArgs.ipAddressWhitelist.length : 0;
goog.events.listen(goog.dom.getRequiredElement('btn-add-ip'),
goog.events.EventType.CLICK,
this.onIpAdd_,
false,
this);
};
/**
* Click handler for IP add button.
* @private
*/
registry.registrar.SecuritySettings.prototype.onIpAdd_ = function() {
var ipInputElt = goog.dom.getRequiredElement('newIp');
var ipElt = goog.soy.renderAsFragment(registry.soy.registrar.security.ip, {
name: 'ipAddressWhitelist[' + this.typeCounts['reg-ips'] + ']',
ip: ipInputElt.value
});
goog.dom.appendChild(goog.dom.getRequiredElement('ips'), ipElt);
var remBtn = goog.dom.getFirstElementChild(ipElt);
goog.dom.classlist.remove(remBtn, goog.getCssName('hidden'));
goog.events.listen(remBtn, goog.events.EventType.CLICK,
goog.bind(this.onIpRemove_, this, remBtn));
this.typeCounts['reg-ips']++;
ipInputElt.value = '';
};
/**
* Click handler for IP remove button.
* @param {!Element} remBtn The remove button.
* @private
*/
registry.registrar.SecuritySettings.prototype.onIpRemove_ =
function(remBtn) {
goog.dom.removeNode(goog.dom.getParentElement(remBtn));
};

View file

@ -0,0 +1,46 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.WhoisSettings');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.whois');
goog.forwardDeclare('registry.registrar.Console');
/**
* WHOIS Settings page.
* @param {!registry.registrar.Console} console
* @param {!registry.Resource} resource the RESTful resource for the registrar.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.WhoisSettings = function(console, resource) {
registry.registrar.WhoisSettings.base(
this, 'constructor', console, resource,
registry.soy.registrar.whois.settings, console.params.isOwner, null);
};
goog.inherits(registry.registrar.WhoisSettings, registry.ResourceComponent);
/** @override */
registry.registrar.WhoisSettings.prototype.bindToDom = function(id) {
registry.registrar.WhoisSettings.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};

View file

@ -0,0 +1,80 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Resource');
goog.require('goog.json');
goog.require('registry.Session');
goog.forwardDeclare('goog.Uri');
/**
* Provide a CRUD view of a server resource.
*
* @param {!goog.Uri} baseUri Target RESTful resource.
* @param {string} id the ID of the target resource
* @param {string} xsrfToken Security token to pass back to the server.
* @extends {registry.Session}
* @constructor
*/
registry.Resource = function(baseUri, id, xsrfToken) {
registry.Resource.base(this, 'constructor', baseUri, xsrfToken);
/** @const @private {string} the ID of the target resource. */
this.id_ = id;
};
goog.inherits(registry.Resource, registry.Session);
/**
* Get the resource from the server.
*
* @param {!Object} args Params for server.
* @param {!Function} callback for retrieved resource.
*/
registry.Resource.prototype.read = function(args, callback) {
this.send_('read', args, callback);
};
/**
* Update the resource on the server.
*
* @param {!Object} args params for server.
* @param {!Function} callback on success.
* @throws {!Exception} if the 'op' field is set on args.
*/
registry.Resource.prototype.update = function(args, callback) {
this.send_('update', args, callback);
};
/**
* RESTful access to resources on the server.
*
* @param {string} opCode One of (create|read|update)
* @param {!Object} argsObj arguments for the operation.
* @param {!Function} callback For XhrIo result throws.
* @private
*/
registry.Resource.prototype.send_ =
function(opCode, argsObj, callback) {
// NB: must be declared this way in order to avoid compiler renaming
var req = {};
req['op'] = opCode;
req['args'] = argsObj;
req['id'] = this.id_;
this.sendXhrIo(goog.json.serialize(req), callback);
};

View file

@ -0,0 +1,191 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.ResourceComponent');
goog.require('goog.dom');
goog.require('goog.json');
goog.require('goog.object');
goog.require('registry.EditItem');
goog.require('registry.forms');
goog.require('registry.util');
goog.forwardDeclare('registry.Console');
goog.forwardDeclare('registry.Resource');
/**
* The ResourceComponent class respresents server state for a named
* resource and binds UI CRUD operations on it, or its constituent
* collection.
* @param {!registry.Console} console console singleton.
* @param {!registry.Resource} resource the RESTful resource.
* @param {!Function} itemTmpl
* @param {boolean} isEditable if true, the "edit" button will be enabled
* @param {Function} renderSetCb may be null if this resource is only
* ever an item.
* @constructor
* @extends {registry.EditItem}
*/
registry.ResourceComponent = function(
console,
resource,
itemTmpl,
isEditable,
renderSetCb) {
registry.ResourceComponent.base(
this, 'constructor', console, itemTmpl, isEditable);
/** @type {!registry.Resource} */
this.resource = resource;
/** @type {Function} */
this.renderSetCb = renderSetCb;
};
goog.inherits(registry.ResourceComponent, registry.EditItem);
/** @override */
registry.ResourceComponent.prototype.renderItem = function(rspObj) {
// Augment the console parameters with the response object, we'll need both.
var params = goog.object.clone(this.console.params);
goog.object.extend(params, rspObj);
registry.ResourceComponent.base(this, 'renderItem', params);
};
/** @override */
registry.ResourceComponent.prototype.bindToDom = function(id) {
registry.ResourceComponent.base(this, 'bindToDom', id);
this.fetchItem(id);
};
/** @override */
registry.ResourceComponent.prototype.back = function() {
this.console.view(this.basePath);
};
/** @override */
registry.ResourceComponent.prototype.fetchItem = function(id) {
this.resource.read({}, goog.bind(this.handleFetchItem, this, id));
};
/** @override */
registry.ResourceComponent.prototype.handleFetchItem = function(id, rsp) {
// XXX: Two different protocols are supported here. The new style used in the
// registrar console is first, followed by the item/set style used by
// the admin console.
if ('status' in rsp) {
// New style.
if (rsp.status == 'SUCCESS') {
this.model = rsp.results[0];
this.model.readonly = true;
this.processItem();
this.renderItem(this.model);
} else {
// XXX: Happens if the server restarts while the client has an
// open-session. This should retry.
registry.forms.resetErrors();
registry.forms.displayError(rsp['message'], rsp['field']);
}
} else if ('item' in rsp) {
this.model = rsp.item;
this.model.readonly = true;
this.processItem();
this.renderItem(this.model);
} else if ('set' in rsp && this.renderSetCb != null) {
// XXX: This conditional logic should be hoisted to edit_item when
// collection support is improved.
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
this.renderSetCb(goog.dom.getRequiredElement('reg-content'), rsp);
} else {
registry.util.log('unknown message type in handleFetchItem');
}
};
/** @override */
registry.ResourceComponent.prototype.sendUpdate = function() {
var modelCopy = /** @type {!Object}
*/ (JSON.parse(goog.json.serialize(this.model)));
this.prepareUpdate(modelCopy);
if (this.id) {
this.resource.update(modelCopy, goog.bind(this.handleUpdateResponse, this));
} else {
this.resource.update(modelCopy, goog.bind(this.handleCreateResponse, this));
}
};
/** @override */
registry.ResourceComponent.prototype.prepareUpdate = function(modelCopy) {
var form = registry.util.parseForm('item');
for (var ndx in form) {
modelCopy[ndx] = form[ndx];
}
};
/** @override */
registry.ResourceComponent.prototype.handleUpdateResponse = function(rsp) {
if (rsp.status) {
if (rsp.status != 'SUCCESS') {
registry.forms.resetErrors();
registry.forms.displayError(rsp['message'], rsp['field']);
return rsp;
}
// XXX: Vestigial state from admin console. Shouldn't be possible to be
// null.
if (this.id) {
this.fetchItem(this.id || '');
this.toggleEdit();
}
return rsp;
}
// XXX: Should be removed when admin console uses new response format.
if (rsp instanceof Object && 'results' in rsp) {
registry.util.butter(rsp['results']);
this.bindToDom(this.nextId || '');
this.nextId = null;
} else {
registry.util.butter(rsp.toString());
}
return rsp;
};
/**
* Handle resource create response.
* @param {!Object} rsp Decoded JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.ResourceComponent.prototype.handleCreateResponse =
goog.abstractMethod;
/**
* Handle resource delete response.
* @param {!Object} rsp Decoded JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.ResourceComponent.prototype.handleDeleteResponse =
goog.abstractMethod;

View file

@ -0,0 +1,107 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Session');
goog.require('goog.json');
goog.require('goog.net.XhrIo');
goog.require('registry.util');
goog.forwardDeclare('goog.Uri');
/**
* XHR launcher for JSON requests.
* @param {!goog.Uri} defaultUri URI to which requests are POSTed.
* @param {string} xsrfToken Cross-site request forgery protection token.
* @constructor
* @template REQUEST, RESPONSE
*/
registry.Session = function(defaultUri, xsrfToken) {
/**
* URI to which requests are posted.
* @protected {!goog.Uri}
* @const
*/
this.uri = defaultUri;
/**
* XHR request headers.
* @private {!Object<string, string>}
* @const
*/
this.headers_ = {
'Content-Type': 'application/json; charset=utf-8',
'X-CSRF-Token': xsrfToken,
'X-Requested-With': 'XMLHttpRequest'
};
};
/**
* Abstract method to send a request to the server.
* @param {REQUEST} body HTTP request body as a string or JSON object.
* @param {function(RESPONSE)} onSuccess XHR success callback.
* @param {function(string)=} opt_onError XHR error callback. The default action
* is to show a bloody butterbar.
* @final
*/
registry.Session.prototype.sendXhrIo =
function(body, onSuccess, opt_onError) {
goog.net.XhrIo.send(
this.uri.toString(),
goog.bind(this.onXhrComplete_, this, onSuccess,
opt_onError || goog.bind(this.displayError_, this)),
'POST',
goog.isObject(body) ? goog.json.serialize(body) : body,
this.headers_);
};
/**
* Handler invoked when an asynchronous request is complete.
* @param {function(RESPONSE)} onSuccess Success callback.
* @param {function(string)} onError Success callback.
* @param {{target: !goog.net.XhrIo}} e XHR event.
* @private
*/
registry.Session.prototype.onXhrComplete_ = function(onSuccess, onError, e) {
if (e.target.isSuccess()) {
onSuccess(/** @type {!RESPONSE} */ (
e.target.getResponseJson(registry.Session.PARSER_BREAKER_)));
} else {
onError(e.target.getLastError());
}
};
/**
* JSON response prefix which prevents evaluation.
* @private {string}
* @const
*/
registry.Session.PARSER_BREAKER_ = ')]}\'\n';
/**
* Displays `message` to user in bloody butterbar.
* @param {string} message
* @private
*/
registry.Session.prototype.displayError_ = function(message) {
registry.util.butter(
this.uri.toString() + ': ' + message + '. Please reload.', true);
};

View file

@ -0,0 +1,216 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.util');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.soy');
/**
* Logging function that delegates to `console.log()`.
* @param {...*} var_args
*/
registry.util.log = function(var_args) {
if (goog.DEBUG) {
if (goog.isDef(goog.global.console) &&
goog.isDef(goog.global.console['log'])) {
goog.global.console.log.apply(goog.global.console, arguments);
}
}
};
/**
* CSS class for hiding an element whose visibility can be toggled.
* @type {string}
* @const
*/
registry.util.cssHidden = goog.getCssName('hidden');
/**
* CSS class for showing an element whose visibility can be toggled.
* @type {string}
* @const
*/
registry.util.cssShown = goog.getCssName('shown');
/**
* Changes element visibility by toggling CSS `shown` to `hidden`.
* @param {!Element|string} element Element or id attribute of element.
* @param {boolean} visible Shows `element` if true, or else hides it.
*/
registry.util.setVisible = function(element, visible) {
goog.dom.classlist.addRemove(
goog.dom.getElement(element),
visible ? registry.util.cssHidden : registry.util.cssShown,
visible ? registry.util.cssShown : registry.util.cssHidden);
};
/**
* Show a buttebar with the given message. A dismiss link will be added.
* @param {string} message the message to show the user.
* @param {boolean=} opt_isFatal indicates butterbar should be blood red. This
* should only be used when an RPC returns a non-200 status.
*/
registry.util.butter = function(message, opt_isFatal) {
goog.dom.setTextContent(
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-text')),
message);
var butterbar =
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar'));
registry.util.setVisible(butterbar, true);
if (opt_isFatal) {
goog.dom.classlist.add(butterbar, goog.getCssName('fatal'));
} else {
goog.dom.classlist.remove(butterbar, goog.getCssName('fatal'));
}
};
/**
* Hides the butterbar.
*/
registry.util.unbutter = function() {
registry.util.setVisible(
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar')),
false);
};
/**
* Renders the tmpl at and then moves it before the given elt.
* @param {Element|string} id dom id of the refElt to render before.
* @param {function()} tmpl template to render.
* @param {!Object} tmplParams params to pass to the template.
* @return {!Element} the rendered row.
*/
registry.util.renderBeforeRow = function(id, tmpl, tmplParams) {
var refElt = goog.dom.getElement(id);
goog.soy.renderElement(refElt, tmpl, tmplParams);
var prevSib = goog.dom.getPreviousElementSibling(refElt);
goog.dom.removeNode(refElt);
refElt.removeAttribute('id');
goog.dom.insertSiblingAfter(refElt, prevSib);
var newAnchorRefElt = goog.dom.createDom(refElt.tagName, {'id': id});
goog.dom.insertSiblingAfter(newAnchorRefElt, refElt);
return refElt;
};
/**
* Turns an HTML form's named elements into a JSON data structure with
* Pablo's black magick.
* @param {string} formName the name of the form to be parsed.
* @throws {Error} if `formName` couldn't be found.
* @return {!Object} the parsed form values as an object.
*/
registry.util.parseForm = function(formName) {
var form = /** @type {HTMLFormElement}
*/ (document.querySelector('form[name=' + formName + ']'));
if (form == null) {
throw new Error('No such form named ' + formName);
}
var obj = {};
// Find names first, since e.g. radio buttons have two elts with the
// same name.
var eltNames = {};
for (var i = 0; i < form.elements.length; i++) {
var elt = form.elements[i];
if (elt.name == '') {
continue;
}
eltNames[elt.name] = null;
}
for (var eltName in eltNames) {
var elt = form.elements[eltName];
var val;
if (elt.type == 'checkbox') {
val = elt.checked;
} else {
val = elt.value;
}
registry.util.expandObject_(obj, eltName, val);
}
return obj;
};
/**
* Give the object a value at the given path. Paths are split on dot
* and array elements are recognized.
*
* <ul>
* <li>a = 1
* <li>foo:a = 1.5
* <li>b.c = 2
* <li>b.d = 3
* <li>c[0] = 4
* <li>c[1] = 5
* <li>d[0].a = 6
* <li>d[1].foo:b = 7
* <li>d[1].@b = 8
* </ul>
*
* Yields the following object:
* <pre>
* {
* a: '1',
* 'foo:a': '1.5',
* b: {
* c: '2',
* d: '3'
* },
* c: ['4','5'],
* d: [{a:'6'},{'foo:b':'7'},{'@b':'8'}]
* }
* </pre>
*
* @param {!Object} obj
* @param {string} pathSpec
* @param {string|Array.<string>|null} val
* @private
*/
registry.util.expandObject_ = function(obj, pathSpec, val) {
var path = pathSpec.split('.');
for (var p = 0; p < path.length; p++) {
var fieldName = path[p];
var arrElt = fieldName.match(/^([\w:]+)\[(\d+)\]$/);
if (arrElt) {
var arrName = arrElt[1];
var arrNdx = arrElt[2];
if (!obj[arrName]) {
obj[arrName] = [];
}
obj = obj[arrName];
fieldName = arrNdx;
if (!obj[arrNdx]) {
obj[arrNdx] = {};
}
} else {
if (!obj[fieldName]) {
obj[fieldName] = {};
}
}
if (p == path.length - 1) {
obj[fieldName] = val;
} else {
obj = obj[fieldName];
}
}
};