This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 25f95930d3 Added Traffic Portal page for CDNi config requests (#6639)
25f95930d3 is described below

commit 25f95930d3ea2aeca59eee1bffc4013226cee59a
Author: mattjackson220 <[email protected]>
AuthorDate: Wed Apr 20 16:14:04 2022 -0600

    Added Traffic Portal page for CDNi config requests (#6639)
    
    * Added Traffic Portal page for CDNi config requests
    
    * updates per comments
    
    * fixed spacing
    
    * updated per comments
    
    * updates per comments
    
    * fixed docs
    
    * fixed note spacing
---
 CHANGELOG.md                                       |  1 +
 .../v4/oc_ci_configuration_request_id_approved.rst |  2 +-
 .../source/api/v4/oc_ci_configuration_requests.rst | 96 ++++++++++++++++++++++
 traffic_ops/traffic_ops_golang/cdni/shared.go      | 86 ++++++++++++++++++-
 traffic_ops/traffic_ops_golang/routing/routes.go   |  5 +-
 traffic_portal/app/src/app.js                      |  5 ++
 traffic_portal/app/src/common/api/CdniService.js   | 75 +++++++++++++++++
 traffic_portal/app/src/common/api/index.js         |  1 +
 .../FormCdniRequestController.js                   | 50 +++++++++++
 .../form.cdniConfigRequests.tpl.html               | 53 ++++++++++++
 .../modules/form/cdniConfigRequests/index.js       | 21 +++++
 .../common/modules/navigation/navigation.tpl.html  |  1 +
 .../cdniConfigRequests/TableCdniController.js      | 68 +++++++++++++++
 .../modules/table/cdniConfigRequests/index.js      | 21 +++++
 .../table.cdniConfigRequests.tpl.html              | 23 ++++++
 .../cdniConfigRequests/cdniConfigRequests.tpl.html | 22 +++++
 .../modules/private/cdniConfigRequests/index.js    | 34 ++++++++
 .../private/cdniConfigRequests/list/index.js       | 42 ++++++++++
 .../private/cdniConfigRequests/view/index.js       | 42 ++++++++++
 19 files changed, 642 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30e5270370..269a30baa5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - Added functionality for login to provide a Bearer token and for that token 
to be later used for authorization.
 - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to 
server for queuing and dequeueing config and revalidate updates.
 - Added layered profile feature to 4.0 for `GET` servers/, `POST` servers/, 
`PUT` servers/{id} and `DELETE` servers/{id}.
+- Added a Traffic Ops endpoint and Traffic Portal page to view all CDNi 
configuration update requests and approve or deny.
 
 ### Fixed
 - Update traffic\_portal dependencies to mitigate `npm audit` issues.
diff --git a/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst 
b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst
index c709871572..7c4ab4cf71 100644
--- a/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst
+++ b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst
@@ -25,7 +25,7 @@ Triggers an asynchronous task to update the configuration for 
the :abbr:`uCDN (U
 
 :Auth. Required: Yes
 :Roles Required: "admin"
-:Permissions Required: CDNI-CAPACITY:ADMIN
+:Permissions Required: CDNI-ADMIN:READ, CDNI-ADMIN:UPDATE
 :Response Type:  Object
 
 Request Structure
diff --git a/docs/source/api/v4/oc_ci_configuration_requests.rst 
b/docs/source/api/v4/oc_ci_configuration_requests.rst
new file mode 100644
index 0000000000..cab8c5e20f
--- /dev/null
+++ b/docs/source/api/v4/oc_ci_configuration_requests.rst
@@ -0,0 +1,96 @@
+..
+..
+.. 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.
+..
+
+.. _to-api-oc-ci-configuration_requests:
+
+********************************
+``OC/CI/configuration/requests``
+********************************
+
+``GET``
+=======
+Returns the requested updates for :abbr:`CDNi (Content Delivery Network 
Interconnect)` configurations. An optional ``id`` parameter will return only 
information for a specific request.
+
+:Auth. Required: Yes
+:Roles Required: "admin"
+:Permissions Required: CDNI-ADMIN:READ
+:Response Type:  Array
+
+Request Structure
+-----------------
+.. table:: Request Query Parameters
+
+       
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
+       | Name      | Required | Description                                    
                                                               |
+       
+===========+==========+===============================================================================================================+
+       | id        | no       | Return only the configuration requests 
identified by this integral, unique identifier                         |
+       
+-----------+----------+---------------------------------------------------------------------------------------------------------------+
+
+Response Structure
+------------------
+:id:                     An integral, unique identifier for the requested 
configuration updates.
+:ucdn:                   The name of the :abbr:`uCDN (Upstream Content 
Delivery Network)` to which the requested changes apply.
+:data:                   An array of generic :abbr:`FCI (Footprint and 
Capabilities Advertisement Interface)` base objects.
+:host:                   The domain to which the requested changes apply.
+:requestType:            A string of the type of configuration update request.
+:asyncStatusId:          An integral, unique identifier for the associated 
asynchronous status.
+:generic-metadata-type:  A string of the type of metadata to follow conforming 
to :rfc:`8006`.
+:generic-metadata-value: An array of generic metadata value objects conforming 
to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications.
+:footprints:             An array of footprints impacted by this generic base 
object.
+
+.. note:: These are meant to be generic and therefore there is not much 
information in these documents. For further information please see :rfc:`8006`, 
:rfc:`8007`, :rfc:`8008`, and the :abbr:`SVA (Streaming Video Alliance)` 
documents titled `Footprint and Capabilities Interface: Open Caching API`, 
`Open Caching API Implementation Guidelines`, `Configuration Interface: Part 1 
Specification - Overview & Architecture`, `Configuration Interface: Part 2 
Specification – CDNi Metadata Model Ex [...]
+
+.. code-block:: json
+       :caption: Example /OC/CI/configuration/requests Response
+
+       {
+               "response": [
+                       {
+                               "id": 1,
+                               "ucdn": "ucdn1",
+                               "data": [
+                                       {
+                                               "generic-metadata-type": 
"MI.RequestedCapacityLimits",
+                                               "generic-metadata-value": {
+                                                       "requested-limits": [
+                                                               {
+                                                                       
"limit-type": "egress",
+                                                                       
"limit-value": 232323,
+                                                                       
"footprints": [
+                                                                               
{
+                                                                               
        "footprint-type": "ipv4cidr",
+                                                                               
        "footprint-value": [
+                                                                               
                "127.0.0.1",
+                                                                               
                "127.0.0.2"
+                                                                               
        ]
+                                                                               
},
+                                                                               
{
+                                                                               
        "footprint-type": "countrycode",
+                                                                               
        "footprint-value": [
+                                                                               
                "us"
+                                                                               
        ]
+                                                                               
}
+                                                                       ]
+                                                               }
+                                                       ]
+                                               }
+                                       }
+                               ],
+                               "host": "example.com",
+                               "requestType": "hostConfigUpdate",
+                               "asyncStatusId": 0
+                       }
+               ]
+       }
diff --git a/traffic_ops/traffic_ops_golang/cdni/shared.go 
b/traffic_ops/traffic_ops_golang/cdni/shared.go
index 75f7bccc4f..305d202576 100644
--- a/traffic_ops/traffic_ops_golang/cdni/shared.go
+++ b/traffic_ops/traffic_ops_golang/cdni/shared.go
@@ -56,8 +56,10 @@ LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id
 LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
 ORDER BY host DESC`
 
-       InsertCapabilityUpdateQuery                    = `INSERT INTO 
cdni_capability_updates (ucdn, data, async_status_id, request_type, host) 
VALUES ($1, $2, $3, $4, $5)`
-       SelectCapabilityUpdateQuery                    = `SELECT ucdn, data, 
async_status_id, request_type, host FROM cdni_capability_updates WHERE id = $1`
+       InsertCapabilityUpdateQuery     = `INSERT INTO cdni_capability_updates 
(ucdn, data, async_status_id, request_type, host) VALUES ($1, $2, $3, $4, $5)`
+       SelectCapabilityUpdateQuery     = `SELECT ucdn, data, async_status_id, 
request_type, host FROM cdni_capability_updates WHERE id = $1`
+       SelectAllCapabilityUpdatesQuery = `SELECT id, ucdn, data, request_type, 
host FROM cdni_capability_updates`
+
        DeleteCapabilityUpdateQuery                    = `DELETE FROM 
cdni_capability_updates WHERE id = $1`
        UpdateTotalLimitsByCapabilityAndLimitTypeQuery = `UPDATE 
cdni_total_limits SET maximum_hard = $1 WHERE capability_id = $2 AND limit_type 
= $3`
        UpdateHostLimitsByCapabilityAndLimitTypeQuery  = `UPDATE 
cdni_host_limits SET maximum_hard = $1 WHERE capability_id = $2 AND limit_type 
= $3 AND host = $4`
@@ -66,6 +68,7 @@ ORDER BY host DESC`
        hostConfigLabel = "hostConfigUpdate"
 )
 
+// GetCapabilities returns the CDNi capability limits.
 func GetCapabilities(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
        if userErr != nil || sysErr != nil {
@@ -127,6 +130,7 @@ func getBearerToken(r *http.Request) string {
        return ""
 }
 
+// PutHostConfiguration adds the requested CDNi configuration update for a 
specific host to the queue and adds an async status.
 func PutHostConfiguration(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"host"}, nil)
        if userErr != nil || sysErr != nil {
@@ -204,6 +208,7 @@ func PutHostConfiguration(w http.ResponseWriter, r 
*http.Request) {
        api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 }
 
+// PutConfiguration adds the requested CDNi configuration update to the queue 
and adds an async status.
 func PutConfiguration(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
        if userErr != nil || sysErr != nil {
@@ -274,6 +279,50 @@ func PutConfiguration(w http.ResponseWriter, r 
*http.Request) {
        api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 }
 
+// GetRequests returns the CDNi configuration update requests.
+func GetRequests(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       var rows *sql.Rows
+       var err error
+
+       idParam := inf.Params["id"]
+       if idParam != "" {
+               id, parseErr := strconv.Atoi(idParam)
+               if parseErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("id must be an integer"), nil)
+                       return
+               }
+               rows, err = inf.Tx.Tx.Query(SelectAllCapabilityUpdatesQuery+" 
WHERE id = $1", id)
+       } else {
+               rows, err = inf.Tx.Tx.Query(SelectAllCapabilityUpdatesQuery)
+       }
+
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("querying for capability update requests: %w", err))
+               return
+       }
+       defer log.Close(rows, "closing capabilities update query")
+       requests := []ConfigurationUpdateRequest{}
+       for rows.Next() {
+               var request ConfigurationUpdateRequest
+               if err := rows.Scan(&request.ID, &request.UCDN, &request.Data, 
&request.RequestType, &request.Host); err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("scanning db rows: %w", err))
+                       return
+               }
+               requests = append(requests, request)
+       }
+
+       api.WriteResp(w, r, requests)
+
+}
+
+// PutConfigurationResponse approves or denies a CDNi configuration request 
and updates the configuration and async status appropriately.
 func PutConfigurationResponse(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"approved", 
"id"}, []string{"id"})
        if userErr != nil || sysErr != nil {
@@ -510,7 +559,7 @@ func checkBearerToken(bearerToken string, inf *api.APIInfo) 
(string, error) {
        }
 
        if token.Audience() == nil || len(token.Audience()) == 0 {
-               return "", errors.New("invalid token - ucdn must be defined in 
audience claim")
+               return "", errors.New("invalid token - dcdn must be defined in 
audience claim")
        }
        if token.Audience()[0] != inf.Config.Cdni.DCdnId {
                return "", errors.New("invalid token - incorrect dcdn")
@@ -634,26 +683,31 @@ func getTelemetryMetricsMap(tx *sql.Tx) 
(map[string][]Metric, error) {
        return telemetryMetricMap, nil
 }
 
+// Capabilities contains an array of CDNi capabilities.
 type Capabilities struct {
        Capabilities []Capability `json:"capabilities"`
 }
 
+// Capability contains information about a CDNi capability.
 type Capability struct {
        CapabilityType  SupportedCapabilities `json:"capability-type"`
        CapabilityValue interface{}           `json:"capability-value"`
        Footprints      []Footprint           `json:"footprints"`
 }
 
+// CapacityCapabilityValue contains the total and host capability limits.
 type CapacityCapabilityValue struct {
        TotalLimits []Limit     `json:"total-limits"`
        HostLimits  []HostLimit `json:"host-limits"`
 }
 
+// HostLimit contains the capacity limit information for a specific host.
 type HostLimit struct {
        Host   string  `json:"host"`
        Limits []Limit `json:"limits"`
 }
 
+// Limit contains the information for a capacity limit.
 type Limit struct {
        LimitType       CapacityLimitType `json:"limit-type"`
        MaximumHard     int64             `json:"maximum-hard"`
@@ -661,15 +715,18 @@ type Limit struct {
        TelemetrySource TelemetrySource   `json:"telemetry-source"`
 }
 
+// TelemetrySource contains the information for a telemetry source.
 type TelemetrySource struct {
        Id     string `json:"id"`
        Metric string `json:"metric"`
 }
 
+// TelemetryCapabilityValue contains an array of telemetry sources.
 type TelemetryCapabilityValue struct {
        Sources []Telemetry `json:"sources"`
 }
 
+// Telemetry contains the information for a telemetry metric.
 type Telemetry struct {
        Id           string              `json:"id"`
        Type         TelemetrySourceType `json:"type"`
@@ -677,6 +734,7 @@ type Telemetry struct {
        Metrics      []Metric            `json:"metrics"`
 }
 
+// Metric contains the metric information for a telemetry metric.
 type Metric struct {
        Name            string `json:"name"`
        TimeGranularity int    `json:"time-granularity"`
@@ -685,12 +743,14 @@ type Metric struct {
        TelemetryId     string `json:"-"`
 }
 
+// Footprint contains the information for a footprint.
 type Footprint struct {
        FootprintType  FootprintType `json:"footprint-type" db:"footprint_type"`
        FootprintValue []string      `json:"footprint-value" 
db:"footprint_value"`
        CapabilityId   int           `json:"-"`
 }
 
+// CapacityLimitType is a string of the capacity limit type.
 type CapacityLimitType string
 
 const (
@@ -702,6 +762,7 @@ const (
        CacheSize                        = "cache-size"
 )
 
+// SupportedCapabilities is a string of the supported capabilities.
 type SupportedCapabilities string
 
 const (
@@ -709,6 +770,7 @@ const (
        FciCapacityLimits                       = "FCI.CapacityLimits"
 )
 
+// SupportedGenericMetadataType is a string of the supported metadata type.
 type SupportedGenericMetadataType string
 
 const (
@@ -723,12 +785,14 @@ func (s SupportedGenericMetadataType) isValid() bool {
        return false
 }
 
+// TelemetrySourceType is a string of the telemetry source type. Right now 
only "generic" is supported.
 type TelemetrySourceType string
 
 const (
        Generic TelemetrySourceType = "generic"
 )
 
+// FootprintType is a string of the footprint type.
 type FootprintType string
 
 const (
@@ -738,32 +802,48 @@ const (
        CountryCode               = "countrycode"
 )
 
+// GenericHostMetadata contains the generic CDNi metadata for a requested 
update to a specific host.
 type GenericHostMetadata struct {
        Host         string           `json:"host"`
        HostMetadata HostMetadataList `json:"host-metadata"`
 }
 
+// GenericRequestMetadata contains the generic CDNi metadata for a requested 
update.
 type GenericRequestMetadata struct {
        Type     string          `json:"type"`
        Metadata json.RawMessage `json:"metadata"`
        Host     string          `json:"host,omitempty"`
 }
 
+// HostMetadataList contains CDNi metadata for a specific host.
 type HostMetadataList struct {
        Metadata json.RawMessage `json:"metadata"`
 }
 
+// GenericMetadata contains generic CDNi metadata.
 type GenericMetadata struct {
        Type  SupportedGenericMetadataType `json:"generic-metadata-type"`
        Value json.RawMessage              `json:"generic-metadata-value"`
 }
 
+// CapacityRequestedLimits contains the requested capacity limits.
 type CapacityRequestedLimits struct {
        RequestedLimits []CapacityLimit `json:"requested-limits"`
 }
 
+// CapacityLimit contains the limit information for a given footprint.
 type CapacityLimit struct {
        LimitType  string      `json:"limit-type"`
        LimitValue int64       `json:"limit-value"`
        Footprints []Footprint `json:"footprints"`
 }
+
+// ConfigurationUpdateRequest contains information about a requested CDNi 
configuration update request.
+type ConfigurationUpdateRequest struct {
+       ID            int             `json:"id"`
+       UCDN          string          `json:"ucdn"`
+       Data          json.RawMessage `json:"data"`
+       Host          string          `json:"host"`
+       RequestType   string          `json:"requestType" db:"request_type"`
+       AsyncStatusID int             `json:"asyncStatusId" 
db:"async_status_id"`
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 149db14d06..cbfa250307 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -134,8 +134,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                // CDNI integration
                {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `OC/FCI/advertisement/?$`, Handler: cdni.GetCapabilities, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"CDNI-CAPACITY:READ"}, Authenticated: Authenticated, Middlewares: nil, 
ID: 541357729077},
                {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/?$`, Handler: cdni.PutConfiguration, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"CDNI-CAPACITY:UPDATE"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 541357729078},
-               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/{host}?$`, Handler: 
cdni.PutHostConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, 
RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 541357729079},
-               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/request/{id}/{approved}?$`, Handler: 
cdni.PutConfigurationResponse, RequiredPrivLevel: auth.PrivLevelAdmin, 
RequiredPermissions: []string{"CDNI-CAPACITY:ADMIN"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 541357729080},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/{host}$`, Handler: 
cdni.PutHostConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, 
RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 541357729079},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/request/{id}/{approved}$`, Handler: 
cdni.PutConfigurationResponse, RequiredPrivLevel: auth.PrivLevelAdmin, 
RequiredPermissions: []string{"CDNI-ADMIN:READ", "CDNI-ADMIN:UPDATE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 541357729080},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `OC/CI/configuration/requests/?$`, Handler: 
cdni.GetRequests, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: 
[]string{"CDNI-ADMIN:READ"}, Authenticated: Authenticated, Middlewares: nil, 
ID: 541357729081},
 
                // SSL Keys
                {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `sslkey_expirations/?$`, Handler: 
deliveryservice.GetSSlKeyExpirationInformation, RequiredPrivLevel: 
auth.PrivLevelAdmin, RequiredPermissions: []string{"SSL-KEY-EXPIRATION:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 41357729075},
diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js
index 61b893f001..1e9b37bc66 100644
--- a/traffic_portal/app/src/app.js
+++ b/traffic_portal/app/src/app.js
@@ -212,6 +212,9 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./modules/private/tenants/users').name,
         require('./modules/private/certExpirations').name,
         require('./modules/private/certExpirations/list').name,
+        require('./modules/private/cdniConfigRequests').name,
+        require('./modules/private/cdniConfigRequests/list').name,
+        require('./modules/private/cdniConfigRequests/view').name,
         require('./modules/private/types').name,
         require('./modules/private/topologies').name,
         require('./modules/private/topologies/cacheGroups').name,
@@ -277,6 +280,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/form/cdn').name,
         require('./common/modules/form/cdn/edit').name,
         require('./common/modules/form/cdn/new').name,
+        require('./common/modules/form/cdniConfigRequests').name,
         require('./common/modules/form/cdnDnssecKeys').name,
         require('./common/modules/form/cdnDnssecKeys/generate').name,
         require('./common/modules/form/cdnDnssecKeys/regenerateKsk').name,
@@ -373,6 +377,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/table/cdnNotifications').name,
         require('./common/modules/table/cdnServers').name,
         require('./common/modules/table/certExpirations').name,
+        require('./common/modules/table/cdniConfigRequests').name,
         require('./common/modules/table/coordinates').name,
         require('./common/modules/table/deliveryServices').name,
         require('./common/modules/table/deliveryServiceCapabilities').name,
diff --git a/traffic_portal/app/src/common/api/CdniService.js 
b/traffic_portal/app/src/common/api/CdniService.js
new file mode 100644
index 0000000000..1d1e319b38
--- /dev/null
+++ b/traffic_portal/app/src/common/api/CdniService.js
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+var CdniService = function($http, ENV, messageModel) {
+
+       this.getCdniConfigRequests = function() {
+               return $http.get(ENV.api.unstable + 
'OC/CI/configuration/requests').then(
+                       function (result) {
+                               return result.data.response;
+                               },
+                       function (err) {
+                               messageModel.setMessages(err.data.alerts, true);
+                               throw err;
+                       }
+               )
+       };
+
+       this.getCdniConfigRequestById = function(id) {
+               return $http.get(ENV.api.unstable + 
'OC/CI/configuration/requests?id=' + id).then(
+                       function (result) {
+                               if (result.data.response.length > 0) {
+                                       return result.data.response[0]
+                               }
+                               return result.data.response;
+                       },
+                       function (err) {
+                               messageModel.setMessages(err.data.alerts, true);
+                               throw err;
+                       }
+               )
+       };
+
+       this.getCurrentCdniConfigByUCDN = function(ucdn) {
+               return $http.get(ENV.api.unstable + 
'OC/FCI/advertisement?ucdn=' + ucdn).then(
+                       function (result) {
+                               return result.data;
+                       },
+                       function (err) {
+                               messageModel.setMessages(err.data.alerts, true);
+                               throw err;
+                       }
+               )
+       };
+
+       this.sendResponseToCdniRequest = function(id, approve) {
+               return $http.put(ENV.api.unstable + 
'OC/CI/configuration/request/' + id + '/' + approve).then(
+                       function (result) {
+                               return result.data.response;
+                       },
+                       function (err) {
+                               messageModel.setMessages(err.data.alerts, true);
+                               throw err;
+                       }
+               )
+       };
+};
+
+CdniService.$inject = ['$http', 'ENV', 'messageModel'];
+module.exports = CdniService;
diff --git a/traffic_portal/app/src/common/api/index.js 
b/traffic_portal/app/src/common/api/index.js
index 66653d9e62..40f2220016 100644
--- a/traffic_portal/app/src/common/api/index.js
+++ b/traffic_portal/app/src/common/api/index.js
@@ -23,6 +23,7 @@ module.exports = angular.module('trafficPortal.api', [])
     .service('cacheGroupService', require('./CacheGroupService'))
     .service('cacheStatsService', require('./CacheStatsService'))
     .service('capabilityService', require('./CapabilityService'))
+    .service('cdniService', require('./CdniService'))
     .service('cdnService', require('./CDNService'))
     .service('certExpirationsService', require('./CertExpirationsService'))
        .service('changeLogService', require('./ChangeLogService'))
diff --git 
a/traffic_portal/app/src/common/modules/form/cdniConfigRequests/FormCdniRequestController.js
 
b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/FormCdniRequestController.js
new file mode 100644
index 0000000000..96397d4c68
--- /dev/null
+++ 
b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/FormCdniRequestController.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+var FormCdniRequestController = function($scope, $stateParams, $uibModal, 
cdniService, cdniRequest, currentConfig, locationUtils, messageModel) {
+       $scope.reqId = $stateParams.reqId;
+       $scope.cdniRequest = cdniRequest;
+       $scope.cdniRequest.data = JSON.stringify($scope.cdniRequest.data, null, 
5);
+       $scope.currentConfig = JSON.stringify(currentConfig, null, 5);
+
+       $scope.navigateToPath = locationUtils.navigateToPath;
+
+       $scope.respondToRequest = function(approve) {
+               const titleStart = approve ? 'Approve' : 'Deny';
+               const params = {
+                       title: `${titleStart} CDNi Update Request: 
${cdniRequest.id}`
+               };
+               const modalInstance = $uibModal.open({
+                       templateUrl: 
'common/modules/dialog/confirm/dialog.confirm.tpl.html',
+                       controller: 'DialogConfirmController',
+                       size: 'md',
+                       resolve: {params}
+               });
+               modalInstance.result.then(function() {
+                       cdniService.sendResponseToCdniRequest(cdniRequest.id, 
approve).then(
+                               function(result) {
+                                       messageModel.setMessages([{level: 
'success', text: result}], true);
+                                       
$scope.navigateToPath('/cdni-config-requests')
+                               });
+               });
+       };
+};
+
+FormCdniRequestController.$inject = ['$scope', '$stateParams', '$uibModal', 
'cdniService', 'cdniRequest', 'currentConfig', 'locationUtils', 'messageModel'];
+module.exports = FormCdniRequestController;
diff --git 
a/traffic_portal/app/src/common/modules/form/cdniConfigRequests/form.cdniConfigRequests.tpl.html
 
b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/form.cdniConfigRequests.tpl.html
new file mode 100644
index 0000000000..76a49e0e0a
--- /dev/null
+++ 
b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/form.cdniConfigRequests.tpl.html
@@ -0,0 +1,53 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+
+<div class="x_panel">
+    <div class="x_title">
+        <ol class="breadcrumb pull-left">
+            <li><a href="#!/cdni-config-requests">CDNi Requests</a></li>
+            <li class="active">{{reqId}}</li>
+        </ol>
+        <div class="clearfix"></div>
+    </div>
+    <div class="x_content">
+        <br>
+        <form name="cdniRequestForm" class="form-horizontal form-label-left" 
novalidate>
+            <div class="form-group">
+                <label for="newData" class="control-label col-md-2 col-sm-2 
col-xs-12">Updated Data</label>
+                <div class="col-md-10 col-sm-10 col-xs-12" style="padding-top: 
10px">
+                    <div class="col-md-10 col-sm-10 col-xs-12">
+                        <textarea id="newData" name="newData" type="text" 
class="form-control" ng-model="cdniRequest.data" rows="25" readonly></textarea>
+                    </div>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="currentConfig" class="control-label col-md-2 
col-sm-2 col-xs-12">Current Data</label>
+                <div class="col-md-10 col-sm-10 col-xs-12" style="padding-top: 
10px">
+                    <div class="col-md-10 col-sm-10 col-xs-12">
+                        <textarea id="currentConfig" name="currentConfig" 
type="text" class="form-control" ng-model="currentConfig" rows="25" 
readonly></textarea>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-success" 
ng-click="respondToRequest(true)">Approve</button>
+                <button type="button" class="btn btn-danger" 
ng-click="respondToRequest(false)">Deny</button>
+            </div>
+        </form>
+    </div>
+</div>
diff --git 
a/traffic_portal/app/src/common/modules/form/cdniConfigRequests/index.js 
b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/index.js
new file mode 100644
index 0000000000..eed0804538
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/cdniConfigRequests/index.js
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+module.exports = angular.module('trafficPortal.form.cdniConfigRequests', [])
+    .controller('FormCdniRequestController', 
require('./FormCdniRequestController'));
diff --git 
a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html 
b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
index 3234328924..d14e3e5a43 100644
--- a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
+++ b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
@@ -50,6 +50,7 @@ under the License.
                         <li class="side-menu-category-item" 
ng-if="hasCapability('params-read')" ng-class="{'current-page': 
isState('trafficPortal.private.parameters')}"><a 
href="/#!/parameters">Parameters</a></li>
                         <li class="side-menu-category-item" 
ng-if="hasCapability('types-read')" ng-class="{'current-page': 
isState('trafficPortal.private.types')}"><a href="/#!/types">Types</a></li>
                         <li class="side-menu-category-item" 
ng-if="hasCapability('statuses-read')" ng-class="{'current-page': 
isState('trafficPortal.private.statuses')}"><a 
href="/#!/statuses">Statuses</a></li>
+                        <li class="side-menu-category-item" 
ng-class="{'current-page': 
isState('trafficPortal.private.cdniConfigRequests')}"><a 
href="/#!/cdni-config-requests">CDNi Requests</a></li>
                     </ul>
                 </li>
                 <li class="side-menu-category"><a 
href="javascript:void(0);"><i class="fa fa-sm fa-chevron-right"></i> 
Topology</a>
diff --git 
a/traffic_portal/app/src/common/modules/table/cdniConfigRequests/TableCdniController.js
 
b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/TableCdniController.js
new file mode 100644
index 0000000000..06ffe6d85e
--- /dev/null
+++ 
b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/TableCdniController.js
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+var TableCdniController = function(tableName, cdniRequests, $scope, 
locationUtils) {
+
+       $scope.cdniRequests = cdniRequests.map(
+               function(x) {
+                       // need to convert this to a date object for ag-grid 
filter to work properly
+                       x.data = JSON.stringify(x.data);
+                       return x;
+               });
+
+       /** The columns of the ag-grid table */
+       $scope.columns = [
+               {
+                       headerName: "Upstream CDN",
+                       field: "ucdn",
+                       hide: false
+               },
+               {
+                       headerName: "Host",
+                       field: "host",
+                       hide: false
+               },
+               {
+                       headerName: "Request Type",
+                       field: "request_type",
+                       hide: false
+               },
+               {
+                       headerName: "New Data",
+                       field: "data",
+                       hide: false
+               }
+       ];
+
+       /** Options, configuration, data and callbacks for the ag-grid table. */
+       $scope.gridOptions = {
+               onRowClick: function(params) {
+                       const selection = window.getSelection().toString();
+                       if(!selection) {
+                               
locationUtils.navigateToPath('/cdni-config-requests/' + params.data.id);
+                               // Event is outside the digest cycle, so we 
need to trigger one.
+                               $scope.$apply();
+                       }
+               }
+       };
+
+};
+
+TableCdniController.$inject = ['tableName', 'cdniRequests', '$scope', 
'locationUtils'];
+module.exports = TableCdniController;
diff --git 
a/traffic_portal/app/src/common/modules/table/cdniConfigRequests/index.js 
b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/index.js
new file mode 100644
index 0000000000..4b9a07aea5
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/index.js
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+module.exports = angular.module('trafficPortal.table.cdniConfigRequests', [])
+    .controller('TableCdniController', require('./TableCdniController'));
diff --git 
a/traffic_portal/app/src/common/modules/table/cdniConfigRequests/table.cdniConfigRequests.tpl.html
 
b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/table.cdniConfigRequests.tpl.html
new file mode 100644
index 0000000000..a6d9baf194
--- /dev/null
+++ 
b/traffic_portal/app/src/common/modules/table/cdniConfigRequests/table.cdniConfigRequests.tpl.html
@@ -0,0 +1,23 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+
+<div class="x_panel">
+    <common-grid-controller table-title="CDNi Update Requests" 
table-name="cdniConfigRequests" options="gridOptions" data="cdniRequests"
+                            columns="columns"></common-grid-controller>
+</div>
diff --git 
a/traffic_portal/app/src/modules/private/cdniConfigRequests/cdniConfigRequests.tpl.html
 
b/traffic_portal/app/src/modules/private/cdniConfigRequests/cdniConfigRequests.tpl.html
new file mode 100644
index 0000000000..a71d5ff3dd
--- /dev/null
+++ 
b/traffic_portal/app/src/modules/private/cdniConfigRequests/cdniConfigRequests.tpl.html
@@ -0,0 +1,22 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+
+<div id="cdniConfigRequestsContainer">
+    <div ui-view="cdniConfigRequestsContent"></div>
+</div>
diff --git a/traffic_portal/app/src/modules/private/cdniConfigRequests/index.js 
b/traffic_portal/app/src/modules/private/cdniConfigRequests/index.js
new file mode 100644
index 0000000000..0bc0f2fc35
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/cdniConfigRequests/index.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+module.exports = angular.module('trafficPortal.private.cdniConfigRequests', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.cdniConfigRequests', {
+                url: 'cdni-config-requests',
+                abstract: true,
+                views: {
+                    privateContent: {
+                        templateUrl: 
'modules/private/cdniConfigRequests/cdniConfigRequests.tpl.html'
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git 
a/traffic_portal/app/src/modules/private/cdniConfigRequests/list/index.js 
b/traffic_portal/app/src/modules/private/cdniConfigRequests/list/index.js
new file mode 100644
index 0000000000..c321a63c01
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/cdniConfigRequests/list/index.js
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+module.exports = 
angular.module('trafficPortal.private.cdniConfigRequests.list', [])
+       .config(function($stateProvider, $urlRouterProvider) {
+               $stateProvider
+                       .state('trafficPortal.private.cdniConfigRequests.list', 
{
+                               url: '',
+                               views: {
+                                       cdniConfigRequestsContent: {
+                                               templateUrl: 
'common/modules/table/cdniConfigRequests/table.cdniConfigRequests.tpl.html',
+                                               controller: 
'TableCdniController',
+                                               resolve: {
+                                                       cdniRequests: 
function(cdniService) {
+                                                               return 
cdniService.getCdniConfigRequests();
+                                                       },
+                                                       tableName: function() {
+                                                               return 
'cdni-config-requests';
+                                                       }
+                                               }
+                                       }
+                               }
+                       })
+               ;
+               $urlRouterProvider.otherwise('/');
+       });
diff --git 
a/traffic_portal/app/src/modules/private/cdniConfigRequests/view/index.js 
b/traffic_portal/app/src/modules/private/cdniConfigRequests/view/index.js
new file mode 100644
index 0000000000..42ff7b5ea9
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/cdniConfigRequests/view/index.js
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+module.exports = 
angular.module('trafficPortal.private.cdniConfigRequests.view', [])
+       .config(function($stateProvider, $urlRouterProvider) {
+               $stateProvider
+                       .state('trafficPortal.private.cdniConfigRequests.view', 
{
+                               url: '/{reqId}',
+                               views: {
+                                       cdniConfigRequestsContent: {
+                                               templateUrl: 
'common/modules/form/cdniConfigRequests/form.cdniConfigRequests.tpl.html',
+                                               controller: 
'FormCdniRequestController',
+                                               resolve: {
+                                                       cdniRequest: 
function($stateParams, cdniService) {
+                                                               return 
cdniService.getCdniConfigRequestById($stateParams.reqId);
+                                                       },
+                                                       currentConfig: 
function(cdniRequest, cdniService) {
+                                                               return 
cdniService.getCurrentCdniConfigByUCDN(cdniRequest.ucdn);
+                                                       }
+                                               }
+                                       }
+                               }
+                       })
+               ;
+               $urlRouterProvider.otherwise('/');
+       });

Reply via email to