You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/02/05 18:21:33 UTC
[41/83] [abbrv] [partial] updated to latest Angular-based admin portal
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/charts/sparklines.js
----------------------------------------------------------------------
diff --git a/portal/js/charts/sparklines.js b/portal/js/charts/sparklines.js
new file mode 100644
index 0000000..29551c5
--- /dev/null
+++ b/portal/js/charts/sparklines.js
@@ -0,0 +1,2 @@
+$('.sessions-bar').sparkline([ 3, 5, 6, 3, 4, 5, 6, 7, 8, 4, 3, 5, 6, 3, 4, 5, 6, 7, 8, 4, 3, 5, 6, 3, 4, 5, 6, 7, 8, 4, 3, 5, 6, 3, 4, 5, 6, 7, 8, 4, 3, 5, 6, 3, 4, 5, 6, 7, 8, 4, 3, 5, 6, 3, 4, 5, 6, 7, 8, 1 ], {type: 'bar', barColor: '#c5c5c5', width: '800px', height: 100, barWidth: 12, barSpacing: '1px'});
+// $('.sessions-bar canvas').width('100%')
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/data-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/data/data-controller.js b/portal/js/data/data-controller.js
new file mode 100644
index 0000000..6644a4e
--- /dev/null
+++ b/portal/js/data/data-controller.js
@@ -0,0 +1,304 @@
+'use strict'
+
+AppServices.Controllers.controller('DataCtrl', ['ug', '$scope', '$rootScope', '$location',
+ function (ug, $scope, $rootScope, $location) {
+
+ var init = function () {
+ $scope.verb = 'GET';
+ $scope.display = '';
+
+ $scope.queryBodyDetail = {};
+
+ $scope.queryBodyDisplay = 'none';
+ $scope.queryLimitDisplay = 'block';
+ $scope.queryStringDisplay = 'block';
+ $scope.entitySelected = {};
+ $scope.newCollection = {};
+ $rootScope.queryCollection = {};
+ $scope.data = {};
+ $scope.data.queryPath = '';
+ $scope.data.queryBody = '{ "name":"value" }';
+ $scope.data.searchString = '';
+ $scope.data.queryLimit = '';
+ };
+ var runQuery = function(verb){
+ $scope.loading = true;
+ //get the parameters
+ var queryPath = $scope.removeFirstSlash($scope.data.queryPath || '');
+ var searchString = $scope.data.searchString || '';
+ var queryLimit = $scope.data.queryLimit || '';
+ var body = JSON.parse($scope.data.queryBody || '{}');
+
+ if (verb == 'POST' && $scope.validateJson(true)) {
+ ug.runDataPOSTQuery(queryPath, body);
+ } else if (verb == 'PUT' && $scope.validateJson(true)) {
+ ug.runDataPutQuery(queryPath, searchString, queryLimit, body);
+ } else if (verb == 'DELETE') {
+ ug.runDataDeleteQuery(queryPath, searchString, queryLimit);
+ } else {
+ //GET
+ ug.runDataQuery(queryPath, searchString, queryLimit);
+ }
+ };
+
+ $scope.$on('top-collections-received', function(event, collectionList) {
+ $scope.loading = false;
+ var ignoredCollections = ['events'];
+ //remove some elements from the collection
+ ignoredCollections.forEach(function(ignoredCollection){
+ collectionList.hasOwnProperty(ignoredCollection) //important check that this is objects own property, not from prototype prop inherited
+ && delete collectionList[ignoredCollection]
+ ;
+ });
+ $scope.collectionList = collectionList;
+ $scope.queryBoxesSelected = false;
+ if(!$scope.queryPath){
+ $scope.loadCollection('/'+collectionList[Object.keys(collectionList).sort()[0]].name);
+ }
+ $scope.applyScope();
+ });
+ $scope.$on('error-running-query', function(event) {
+ $scope.loading = false;
+ runQuery('GET');
+ $scope.applyScope();
+ });
+
+
+ $scope.$on('entity-deleted', function(event) {
+ $scope.deleteLoading = false;
+ $rootScope.$broadcast('alert','success', 'Entities deleted sucessfully');
+ $scope.queryBoxesSelected = false;
+ $scope.checkNextPrev();
+ $scope.applyScope();
+
+ });
+ $scope.$on('entity-deleted-error', function(event) {
+ $scope.deleteLoading = false;
+ runQuery('GET');
+ $scope.applyScope();
+ });
+
+ $scope.$on('collection-created',function(){
+ $scope.newCollection.name = '';
+ });
+
+ $scope.$on('query-received', function(event, collection) {
+ $scope.loading = false;
+ $rootScope.queryCollection = collection;
+ ug.getIndexes($scope.data.queryPath);
+ //$rootScope.$broadcast('alert','success', 'API call completed sucessfully');
+
+ $scope.setDisplayType();
+
+ $scope.checkNextPrev();
+ $scope.applyScope();
+ $scope.queryBoxesSelected = false;
+
+ });
+
+ $scope.$on('indexes-received', function(event, indexes) {
+ //todo - do something with the indexes
+ var fred = indexes;
+ });
+
+ $scope.$on('app-changed',function(){
+ init();
+ });
+
+ $scope.setDisplayType = function() {
+ $scope.display = 'generic';
+ }
+
+ $scope.deleteEntitiesDialog = function(modalId){
+ $scope.deleteLoading = false;
+ $scope.deleteEntities($rootScope.queryCollection, 'entity-deleted', 'error deleting entity');
+
+ $scope.hideModal(modalId)
+ };
+
+ $scope.newCollectionDialog = function(modalId){
+ if ($scope.newCollection.name) {
+ ug.createCollection($scope.newCollection.name);
+ ug.getTopCollections();
+ $rootScope.$broadcast('alert', 'success', 'Collection created successfully.');
+ $scope.hideModal(modalId)
+ } else {
+ $rootScope.$broadcast('alert', 'error', 'You must specify a collection name.');
+ }
+ };
+
+ $scope.addToPath = function(uuid){
+ $scope.data.queryPath = '/' + $rootScope.queryCollection._type + '/' + uuid;
+ }
+
+ $scope.isDeep = function(item){
+ //can be better, just see if object for now
+ return (Object.prototype.toString.call(item) === "[object Object]");
+ };
+
+ //get collection list
+ $scope.loadCollection = function(type) {
+ $scope.data.queryPath = '/'+type.substring(1,type.length);
+ $scope.data.searchString = '';
+ $scope.data.queryLimit = '';
+ $scope.data.body = '{ "name":"value" }';
+ $scope.selectGET();
+ $scope.applyScope();
+ $scope.run();
+ }
+ $scope.selectGET = function() {
+ $scope.queryBodyDisplay = 'none';
+ $scope.queryLimitDisplay = 'block';
+ $scope.queryStringDisplay = 'block';
+ $scope.verb = 'GET';
+ }
+ $scope.selectPOST = function() {
+ $scope.queryBodyDisplay = 'block';
+ $scope.queryLimitDisplay = 'none';
+ $scope.queryStringDisplay = 'none';
+ $scope.verb = 'POST';
+ }
+ $scope.selectPUT = function() {
+ $scope.queryBodyDisplay = 'block';
+ $scope.queryLimitDisplay = 'block';
+ $scope.queryStringDisplay = 'block';
+ $scope.verb = 'PUT';
+ }
+ $scope.selectDELETE = function() {
+ $scope.queryBodyDisplay = 'none';
+ $scope.queryLimitDisplay = 'block';
+ $scope.queryStringDisplay = 'block';
+ $scope.verb = 'DELETE';
+ }
+
+ $scope.validateJson = function(skipMessage) {
+ var queryBody = $scope.data.queryBody;
+
+ try {
+ queryBody = JSON.parse(queryBody);
+ } catch (e) {
+ $rootScope.$broadcast('alert', 'error', 'JSON is not valid');
+ return false;
+ }
+
+ queryBody = JSON.stringify(queryBody,null,2);
+
+ !skipMessage && $rootScope.$broadcast('alert','success', 'JSON is valid');
+
+ $scope.data.queryBody = queryBody;
+ return true;
+ }
+
+
+ $scope.saveEntity = function(entity){
+ if (!$scope.validateJson()) {
+ return false;
+ }
+ var queryBody = entity._json;
+ queryBody = JSON.parse(queryBody);
+ $rootScope.selectedEntity.set(); //clears out all entities
+ $rootScope.selectedEntity.set(queryBody);
+ $rootScope.selectedEntity.set('type', entity._data.type);
+ $rootScope.selectedEntity.set('uuid', entity._data.uuid);
+ $rootScope.selectedEntity.save(function(err, data){
+ if (err) {
+ $rootScope.$broadcast('alert', 'error', 'error: ' + data.error_description);
+ } else {
+ $rootScope.$broadcast('alert', 'success', 'entity saved');
+ }
+
+ });
+ }
+
+ $scope.run = function() {
+ //clear the current data
+ $rootScope.queryCollection = '';
+
+ var verb = $scope.verb;
+ runQuery(verb);
+ }
+
+ $scope.hasProperty = function(prop){
+ //checks each object in array for a given property - used for hiding/showing UI data in table
+ var retval = false;
+ if(typeof $rootScope.queryCollection._list !== 'undefined'){
+ angular.forEach($rootScope.queryCollection._list, function(value, key){
+ //no way to break angular loop :(
+ if(!retval){
+ if(value._data[prop]){
+ retval = true;
+ }
+ }
+ });
+ }
+ return retval;
+ }
+
+ $scope.resetNextPrev = function() {
+ $scope.previous_display = 'none';
+ $scope.next_display = 'none';
+ }
+
+ $scope.checkNextPrev = function() {
+ $scope.resetNextPrev();
+ if ($rootScope.queryCollection.hasPreviousPage()) {
+ $scope.previous_display = 'default';
+ }
+ if($rootScope.queryCollection.hasNextPage()) {
+ $scope.next_display = 'default';
+ }
+ }
+
+ $scope.selectEntity = function(uuid){
+ $rootScope.selectedEntity = $rootScope.queryCollection.getEntityByUUID(uuid);
+ $scope.addToPath(uuid);
+ }
+
+ $scope.getJSONView = function(entity){
+ var tempjson = entity.get();
+
+ //rip out the system elements. Stringify first because we don't want to rip them out of the actual object
+ var queryBody = JSON.stringify(tempjson, null, 2);
+ queryBody = JSON.parse(queryBody);
+ delete queryBody.metadata;
+ delete queryBody.uuid;
+ delete queryBody.created;
+ delete queryBody.modified;
+ delete queryBody.type;
+
+ $scope.queryBody = JSON.stringify(queryBody, null, 2);
+ }
+
+ $scope.getPrevious = function () {
+ $rootScope.queryCollection.getPreviousPage(function(err) {
+ if (err) {
+ $rootScope.$broadcast('alert', 'error', 'error getting previous page of data');
+ }
+ $scope.checkNextPrev();
+ $scope.applyScope();
+ });
+ };
+
+ $scope.getNext = function () {
+ $rootScope.queryCollection.getNextPage(function(err) {
+ if (err) {
+ $rootScope.$broadcast('alert', 'error', 'error getting next page of data');
+ }
+ $scope.checkNextPrev();
+ $scope.applyScope();
+ });
+ };
+
+ init();
+ //persistent vars (stay around when you drill down to an entity detail view)
+ $rootScope.queryCollection = $rootScope.queryCollection || {};
+ $rootScope.selectedEntity = {};
+ if ($rootScope.queryCollection && $rootScope.queryCollection._type) {
+ $scope.loadCollection($rootScope.queryCollection._type);
+ $scope.setDisplayType();
+ }
+ ug.getTopCollections();
+ $scope.resetNextPrev();
+
+
+ }]);
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/data.html
----------------------------------------------------------------------
diff --git a/portal/js/data/data.html b/portal/js/data/data.html
new file mode 100644
index 0000000..2a0158c
--- /dev/null
+++ b/portal/js/data/data.html
@@ -0,0 +1,121 @@
+<div class="content-page">
+
+ <bsmodal id="newCollection"
+ title="Create new collection"
+ close="hideModal"
+ closelabel="Cancel"
+ extrabutton="newCollectionDialog"
+ extrabuttonlabel="Create"
+ buttonid="collection"
+ ng-cloak>
+ <fieldset>
+ <div class="control-group">
+ <label for="new-collection-name">Collection Name:</label>
+ <div class="controls">
+ <input type="text" ug-validate required ng-pattern="collectionNameRegex" ng-attr-title="{{collectionNameRegexDescription}}" ng-model="$parent.newCollection.name" name="collection" id="new-collection-name" class="input-xlarge"/>
+ <p class="help-block hide"></p>
+ </div>
+ </div>
+ </fieldset>
+ </bsmodal>
+
+ <section class="row-fluid">
+
+ <div class="span12">
+ <div class="page-filters">
+ <h1 class="title" class="pull-left"><i class="pictogram title">📾</i> Collections</h1>
+ </div>
+ </div>
+
+ </section>
+ <section class="row-fluid">
+ <div class="span3 user-col">
+ <a class="btn btn-primary" id="new-collection-link" ng-click="showModal('newCollection')">New Collection</a>
+ <ul class="user-list">
+ <li ng-class="queryCollection._type === entity.name ? 'selected' : ''" ng-repeat="entity in collectionList" ng-click="loadCollection('/'+entity.name);">
+ <a id="collection-{{entity.name}}-link" href="javaScript:void(0)">/{{entity.name}} </a>
+ </li>
+ </ul>
+
+ </div>
+
+ <div class="span9 tab-content">
+ <div class="content-page">
+ <form name="dataForm" ng-submit="run();">
+ <fieldset>
+ <div class="control-group">
+ <div class="" data-toggle="buttons-radio">
+ <!--a class="btn" id="button-query-back">◀ Back</a-->
+ <!--Added disabled class to change the way button looks but their functionality is as usual -->
+ <label class="control-label" style="display:none"><strong>Method</strong> <a id="query-method-help" href="#" class="help-link">get help</a></label>
+ <input type="radio" id="create-rb" name="query-action" style="margin-top: -2px;" ng-click="selectPOST();" ng-checked="verb=='POST'"> CREATE
+ <input type="radio" id="read-rb" name="query-action" style="margin-top: -2px;" ng-click="selectGET();" ng-checked="verb=='GET'"> READ
+ <input type="radio" id="update-rb" name="query-action" style="margin-top: -2px;" ng-click="selectPUT();" ng-checked="verb=='PUT'"> UPDATE
+ <input type="radio" id="delete-rb" name="query-action" style="margin-top: -2px;" ng-click="selectDELETE();" ng-checked="verb=='DELETE'"> DELETE
+ </div>
+ </div>
+
+ <div class="control-group">
+ <strong>Path </strong>
+ <div class="controls">
+ <input ng-model="data.queryPath" type="text" ug-validate id="pathDataQuery" ng-attr-title="{{pathRegexDescription}}" ng-pattern="pathRegex" class="span6" autocomplete="off" placeholder="ex: /users" required/>
+ </div>
+ </div>
+ <div class="control-group">
+ <a id="back-to-collection" class="outside-link" style="display:none">Back to collection</a>
+ </div>
+ <div class="control-group">
+ <strong>Query</strong>
+ <div class="controls">
+ <input ng-model="data.searchString" type="text" class="span6" autocomplete="off" placeholder="ex: select * where name='fred'"/>
+ <div style="display:none">
+ <a class="btn dropdown-toggle " data-toggle="dropdown">
+ <span id="query-collections-caret" class="caret"></span>
+ </a>
+ <ul id="query-collections-indexes-list" class="dropdown-menu ">
+ </ul>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="control-group" ng-show="verb=='GET' || verb=='DELETE'">
+ <label class="control-label" for="query-limit"><strong>Limit</strong> <a id="query-limit-help" href="#" ng-show="false" class="help-link">get help</a></label>
+ <div class="controls">
+ <div class="input-append">
+ <input ng-model="data.queryLimit" type="text" class="span5" id="query-limit" placeholder="ex: 10">
+ </div>
+ </div>
+ </div>
+
+ <div class="control-group" style="display:{{queryBodyDisplay}}">
+ <label class="control-label" for="query-source"><strong>JSON Body</strong> <a id="query-json-help" href="#" ng-show="false" class="help-link">get help</a></label>
+ <div class="controls">
+ <textarea ng-model="data.queryBody" id="query-source" class="span6 pull-left" rows="4">
+ { "name":"value" }
+ </textarea>
+ <br>
+ <a class="btn pull-left" ng-click="validateJson();">Validate JSON</a>
+ </div>
+ </div>
+ <div style="clear: both; height: 10px;"></div>
+ <div class="control-group">
+ <input type="submit" ng-disabled="!dataForm.$valid || loading" class="btn btn-primary" id="button-query" value="{{loading ? loadingText : 'Run Query'}}"/>
+ </div>
+ </fieldset>
+ </form>
+ <div ng-include="display=='generic' ? 'data/display-generic.html' : ''"></div>
+ <div ng-include="display=='users' ? 'data/display-users.html' : ''"></div>
+ <div ng-include="display=='groups' ? 'data/display-groups.html' : ''"></div>
+ <div ng-include="display=='roles' ? 'data/display-roles.html' : ''"></div>
+
+ </div>
+
+ </div>
+ </section>
+
+
+
+
+</div>
+
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/display-generic.html
----------------------------------------------------------------------
diff --git a/portal/js/data/display-generic.html b/portal/js/data/display-generic.html
new file mode 100644
index 0000000..ce4f4f6
--- /dev/null
+++ b/portal/js/data/display-generic.html
@@ -0,0 +1,95 @@
+
+
+<bsmodal id="deleteEntities"
+ title="Are you sure you want to delete the entities(s)?"
+ close="hideModal"
+ closelabel="Cancel"
+ extrabutton="deleteEntitiesDialog"
+ extrabuttonlabel="Delete"
+ buttonid="del-entity"
+ ng-cloak>
+ <fieldset>
+ <div class="control-group">
+ </div>
+ </fieldset>
+</bsmodal>
+
+<span class="button-strip">
+ <button class="btn btn-primary" ng-disabled="!valueSelected(queryCollection._list) || deleteLoading" ng-click="deleteEntitiesDialog()">{{deleteLoading ? loadingText : 'Delete Entity(s)'}}</button>
+</span>
+<table class="table table-striped collection-list">
+ <thead>
+ <tr class="table-header">
+ <th><input type="checkbox" ng-show="queryCollection._list.length > 0" id="selectAllCheckbox" ng-model="queryBoxesSelected" ng-click="selectAllEntities(queryCollection._list,$parent,'queryBoxesSelected',true)"></th>
+ <th ng-if="hasProperty('name')">Name</th>
+ <th>UUID</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody ng-repeat="entity in queryCollection._list">
+ <tr class="zebraRows" >
+ <td>
+ <input
+ type="checkbox"
+ id="entity-{{entity._data.name}}-cb"
+ ng-value="entity._data.uuid"
+ ng-model="entity.checked"
+ >
+ </td>
+ <td ng-if="hasProperty('name')">{{entity._data.name}}</td>
+ <td>{{entity._data.uuid}}</td>
+ <td><a href="javaScript:void(0)" ng-click="entitySelected[$index] = !entitySelected[$index];selectEntity(entity._data.uuid)">{{entitySelected[$index] ? 'Hide' : 'View'}} Details</a></td>
+ </tr>
+ <tr ng-if="entitySelected[$index]">
+ <td colspan="5">
+
+
+ <h4 style="margin: 0 0 20px 0">Entity Detail</h4>
+
+
+ <ul class="formatted-json">
+ <li ng-repeat="(k,v) in entity._data track by $index">
+ <span class="key">{{k}} :</span>
+ <!--todo - doing manual recursion to get this out the door for launch, please fix-->
+ <span ng-switch on="isDeep(v)">
+ <ul ng-switch-when="true">
+ <li ng-repeat="(k2,v2) in v"><span class="key">{{k2}} :</span>
+
+ <span ng-switch on="isDeep(v2)">
+ <ul ng-switch-when="true">
+ <li ng-repeat="(k3,v3) in v2"><span class="key">{{k3}} :</span><span class="value">{{v3}}</span></li>
+ </ul>
+ <span ng-switch-when="false">
+ <span class="value">{{v2}}</span>
+ </span>
+ </span>
+ </li>
+ </ul>
+ <span ng-switch-when="false">
+ <span class="value">{{v}}</span>
+ </span>
+ </span>
+ </li>
+ </ul>
+
+ <div class="control-group">
+ <h4 style="margin: 20px 0 20px 0">Edit Entity</h4>
+ <div class="controls">
+ <textarea ng-model="entity._json" class="span12" rows="12"></textarea>
+ <br>
+ <a class="btn btn-primary toolbar pull-left" ng-click="validateJson();">Validate JSON</a><button type="button" class="btn btn-primary pull-right" id="button-query" ng-click="saveEntity(entity);">Save</button>
+ </div>
+ </div>
+ </td>
+ </tr>
+
+ <tr ng-show="queryCollection._list.length == 0">
+ <td colspan="4">No data found</td>
+ </tr>
+ </tbody>
+</table>
+<div style="padding: 10px 5px 10px 5px">
+ <button class="btn btn-primary toolbar" ng-click="getPrevious()" style="display:{{previous_display}}">< Previous</button>
+ <button class="btn btn-primary toolbar" ng-click="getNext()" style="display:{{next_display}}; float:right;">Next ></button>
+</div>
+
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/display-groups.html
----------------------------------------------------------------------
diff --git a/portal/js/data/display-groups.html b/portal/js/data/display-groups.html
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/display-roles.html
----------------------------------------------------------------------
diff --git a/portal/js/data/display-roles.html b/portal/js/data/display-roles.html
new file mode 100644
index 0000000..91ce3be
--- /dev/null
+++ b/portal/js/data/display-roles.html
@@ -0,0 +1 @@
+roles---------------------------------
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/display-users.html
----------------------------------------------------------------------
diff --git a/portal/js/data/display-users.html b/portal/js/data/display-users.html
new file mode 100644
index 0000000..278e303
--- /dev/null
+++ b/portal/js/data/display-users.html
@@ -0,0 +1,88 @@
+
+<table id="query-response-table" class="table">
+ <tbody>
+ <tr class="zebraRows users-row">
+ <td class="checkboxo">
+ <input type="checkbox" onclick="Usergrid.console.selectAllEntities(this);"></td>
+ <td class="gravatar50-td"> </td>
+ <td class="user-details bold-header">Username</td>
+ <td class="user-details bold-header">Display Name</td>
+ <td class="user-details bold-header">UUID</td>
+ <td class="view-details"> </td>
+ </tr>
+ <tr class="zebraRows users-row">
+ <td class="checkboxo">
+ <input class="listItem" type="checkbox" name="/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7" value="bf9a95da-d508-11e2-bf44-236d2eee13a7">
+ </td>
+ <td class="gravatar50-td">
+ <img src="http://www.gravatar.com/avatar/01b37aa66496988ca780b3f515bc768e" class="gravatar50">
+ </td>
+ <td class="details">
+ <a onclick="Usergrid.console.getCollection('GET', '/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/'+'bf9a95da-d508-11e2-bf44-236d2eee13a7'); $('#data-explorer').show(); return false;" class="view-details">10</a>
+ </td>
+ <td class="details"> #"><img src=x onerror=prompt(1);> </td>
+ <td class="details"> bf9a95da-d508-11e2-bf44-236d2eee13a7 </td>
+ <td class="view-details">
+ <a href="" onclick="$('#query-row-bf9a95da-d508-11e2-bf44-236d2eee13a7').toggle(); $('#data-explorer').show(); return false;" class="view-details">Details</a>
+ </td>
+ </tr>
+ <tr id="query-row-bf9a95da-d508-11e2-bf44-236d2eee13a7" style="display:none">
+ <td colspan="5">
+ <div>
+ <div style="padding-bottom: 10px;">
+ <button type="button" class="btn btn-small query-button active" id="button-query-show-row-JSON" onclick="Usergrid.console.activateQueryRowJSONButton(); $('#query-row-JSON-bf9a95da-d508-11e2-bf44-236d2eee13a7').show(); $('#query-row-content-bf9a95da-d508-11e2-bf44-236d2eee13a7').hide(); return false;">JSON</button>
+ <button type="button" class="btn btn-small query-button disabled" id="button-query-show-row-content" onclick="Usergrid.console.activateQueryRowContentButton();$('#query-row-content-bf9a95da-d508-11e2-bf44-236d2eee13a7').show(); $('#query-row-JSON-bf9a95da-d508-11e2-bf44-236d2eee13a7').hide(); return false;">Content</button>
+ </div>
+ <div id="query-row-JSON-bf9a95da-d508-11e2-bf44-236d2eee13a7">
+ <pre>{
+ "picture": "http://www.gravatar.com/avatar/01b37aa66496988ca780b3f515bc768e",
+ "uuid": "bf9a95da-d508-11e2-bf44-236d2eee13a7",
+ "type": "user",
+ "name": "#"><img src=x onerror=prompt(1);>",
+ "created": 1371224432557,
+ "modified": 1371851347024,
+ "username": "10",
+ "email": "fdsafdsa@ookfd.com",
+ "activated": "true",
+ "adr": {
+ "addr1": "",
+ "addr2": "",
+ "city": "",
+ "state": "",
+ "zip": "",
+ "country": ""
+ },
+ "metadata": {
+ "path": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7",
+ "sets": {
+ "rolenames": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/rolenames",
+ "permissions": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/permissions"
+ },
+ "collections": {
+ "activities": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/activities",
+ "devices": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/devices",
+ "feed": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/feed",
+ "groups": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/groups",
+ "roles": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/roles",
+ "following": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/following",
+ "followers": "/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/followers"
+ }
+ },
+ "title": "#"><img src=x onerror=prompt(1);>"
+}</pre>
+ </div>
+ <div id="query-row-content-bf9a95da-d508-11e2-bf44-236d2eee13a7" style="display:none">
+ <table>
+ <tbody>
+ <tr>
+ <td>picture</td>
+ <td>http://www.gravatar.com/avatar/01b37aa66496988ca780b3f515bc768e</td></tr><tr><td>uuid</td><td>bf9a95da-d508-11e2-bf44-236d2eee13a7</td></tr><tr><td>type</td><td>user</td></tr><tr><td>name</td><td>#&quot;&gt;&lt;img src=x onerror=prompt(1);&gt;</td></tr><tr><td>created</td><td>1371224432557</td></tr><tr><td>modified</td><td>1371851347024</td></tr><tr><td>username</td><td>10</td></tr><tr><td>email</td><td>fdsafdsa@ookfd.com</td></tr><tr><td>activated</td><td>true</td></tr><tr><td></td><td style="padding: 0"><table><tbody><tr></tr><tr><td>addr1</td><td></td></tr><tr><td>addr2</td><td></td></tr><tr><td>city</td><td></td></tr><tr><td>state</td><td></td></tr><tr><td>zip</td><td></td></tr><tr><td>country</td><td></td></tr></tbody></table></td></tr><tr><td></td><td style="padding: 0"><table><tbody><tr></tr><tr><td>path</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7</td></tr><tr><td></td><td style="padding:
0"><table><tbody><tr></tr><tr><td>rolenames</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/rolenames</td></tr><tr><td>permissions</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/permissions</td></tr></tbody></table></td></tr><tr><td></td><td style="padding: 0"><table><tbody><tr></tr><tr><td>activities</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/activities</td></tr><tr><td>devices</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/devices</td></tr><tr><td>feed</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/feed</td></tr><tr><td>groups</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/groups</td></tr><tr><td>roles</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95d
a-d508-11e2-bf44-236d2eee13a7/roles</td></tr><tr><td>following</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/following</td></tr><tr><td>followers</td><td>/users/8bb9a3fa-d508-11e2-875d-a59031a365e8/following/bf9a95da-d508-11e2-bf44-236d2eee13a7/followers</td></tr></tbody></table></td></tr></tbody></table></td></tr><tr><td>title</td><td>#&quot;&gt;&lt;img src=x onerror=prompt(1);&gt;</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/entity-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/data/entity-controller.js b/portal/js/data/entity-controller.js
new file mode 100644
index 0000000..0d741d6
--- /dev/null
+++ b/portal/js/data/entity-controller.js
@@ -0,0 +1,69 @@
+'use strict'
+
+AppServices.Controllers.controller('EntityCtrl', ['ug', '$scope', '$rootScope', '$location',
+ function (ug, $scope, $rootScope, $location) {
+
+ if (!$rootScope.selectedEntity) {
+ $location.path('/data');
+ return;
+ }
+
+ $scope.entityUUID = $rootScope.selectedEntity.get('uuid');
+ $scope.entityType = $rootScope.selectedEntity.get('type');
+ var tempjson = $rootScope.selectedEntity.get();
+
+ //rip out the system elements. Stringify first because we don't want to rip them out of the actual object
+ var queryBody = JSON.stringify(tempjson, null, 2);
+ queryBody = JSON.parse(queryBody);
+ delete queryBody.metadata;
+ delete queryBody.uuid;
+ delete queryBody.created;
+ delete queryBody.modified;
+ delete queryBody.type;
+
+ $scope.queryBody = JSON.stringify(queryBody, null, 2);
+
+ $scope.validateJson = function() {
+ var queryBody = $scope.queryBody;
+
+ try {
+ queryBody = JSON.parse(queryBody);
+ } catch (e) {
+ $rootScope.$broadcast('alert', 'error', 'JSON is not valid');
+ return false;
+ }
+
+ queryBody = JSON.stringify(queryBody,null,2);
+
+ $rootScope.$broadcast('alert','success', 'JSON is valid');
+
+ $scope.queryBody = queryBody;
+ return true;
+
+ }
+
+ $scope.saveEntity = function(){
+ if (!$scope.validateJson()) {
+ return false;
+ }
+ var queryBody = $scope.queryBody;
+ queryBody = JSON.parse(queryBody);
+ $rootScope.selectedEntity.set(); //clears out all entities
+ $rootScope.selectedEntity.set(queryBody);
+ $rootScope.selectedEntity.set('type', $scope.entityType);
+ $rootScope.selectedEntity.set('uuid', $scope.entityUUID);
+ $rootScope.selectedEntity.save(function(err, data){
+ if (err) {
+ $rootScope.$broadcast('alert', 'error', 'error: ' + data.error_description);
+ } else {
+ $rootScope.$broadcast('alert', 'success', 'entity saved');
+ }
+
+ });
+ }
+
+
+
+
+
+ }]);
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/entity.html
----------------------------------------------------------------------
diff --git a/portal/js/data/entity.html b/portal/js/data/entity.html
new file mode 100644
index 0000000..6b9d476
--- /dev/null
+++ b/portal/js/data/entity.html
@@ -0,0 +1,31 @@
+<div class="content-page">
+
+ <h4>Entity Detail</h4>
+ <div class="well">
+ <a href="#!/data" class="outside-link"><< Back to collection</a>
+ </div>
+ <fieldset>
+ <div class="control-group">
+ <strong>Path </strong>
+ <div class="controls">
+ {{entityType}}/{{entityUUID}}
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label" for="query-source"><strong>JSON Body</strong></label>
+ <div class="controls">
+ <textarea ng-model="queryBody" class="span6 pull-left" rows="12">{{queryBody}}</textarea>
+ <br>
+ <a class="btn pull-left" ng-click="validateJson();">Validate JSON</a>
+ </div>
+ </div>
+ <div style="clear: both; height: 10px;"></div>
+ <div class="control-group">
+ <button type="button" class="btn btn-primary" id="button-query" ng-click="saveEntity();">Save</button>
+ <!--button type="button" class="btn btn-primary" id="button-query" ng-click="run();">Delete</button-->
+ </div>
+ </fieldset>
+
+</div>
+
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/shell-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/data/shell-controller.js b/portal/js/data/shell-controller.js
new file mode 100644
index 0000000..b06bd9b
--- /dev/null
+++ b/portal/js/data/shell-controller.js
@@ -0,0 +1,9 @@
+'use strict'
+
+AppServices.Controllers.controller('ShellCtrl', ['ug', '$scope', '$rootScope', '$location',
+ function (ug, $scope, $rootScope, $location) {
+
+
+
+
+ }]);
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/data/shell.html
----------------------------------------------------------------------
diff --git a/portal/js/data/shell.html b/portal/js/data/shell.html
new file mode 100644
index 0000000..b977d81
--- /dev/null
+++ b/portal/js/data/shell.html
@@ -0,0 +1,16 @@
+<div class="content-page">
+ <div class="well">
+ <h2>Interactive Shell</h2>
+ <div style="float:right"><a target="_blank" href="http://apigee.com/docs/usergrid/content/usergrid-admin-portal" class="notifications-links">Learn more in our docs</a></div>
+ </div>
+
+ <div class="console-section-contents">
+ <div id="shell-input-div">
+ <p> Type "help" to view a list of the available commands.</p><hr>
+ <span> >> </span>
+ <!--textarea id="shell-input" rows="2" autofocus="autofocus"></textarea-->
+ </div>
+ <pre id="shell-output" class="prettyprint lang-js" style="overflow-x: auto; height: 400px;"><span class="pln"> </span><p><span class="pln"> </span><span class="typ">Response</span><span class="pun">:</span></p><hr><span class="pln">
+ </span></pre>
+ </div>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/dialogs/balloon-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/dialogs/balloon-directive.js b/portal/js/dialogs/balloon-directive.js
new file mode 100644
index 0000000..1145622
--- /dev/null
+++ b/portal/js/dialogs/balloon-directive.js
@@ -0,0 +1,25 @@
+'use strict';
+
+AppServices.Directives.directive('balloon', ['$window','$timeout', function ($window,$timeout) {
+ return{
+ restrict: 'ECA',
+ scope: '=',
+ template: '' +
+ '<div class="baloon {{direction}}" ng-transclude>' +
+ '</div>',
+ replace: true,
+ transclude: true,
+ link: function linkFn(scope, lElement, attrs) {
+ scope.direction = attrs.direction;
+ var runScroll = true;
+ var windowEl = angular.element($window);
+ windowEl.on('scroll', function() {
+ if(runScroll){
+ lElement.addClass('fade-out');
+ $timeout(function(){lElement.addClass('hide')},1000)
+ runScroll = false;
+ }
+ });
+ }
+ }
+}])
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/dialogs/modal-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/dialogs/modal-directive.js b/portal/js/dialogs/modal-directive.js
new file mode 100644
index 0000000..2b1619c
--- /dev/null
+++ b/portal/js/dialogs/modal-directive.js
@@ -0,0 +1,41 @@
+'use strict';
+
+AppServices.Directives.directive('bsmodal', ["$rootScope", function ($rootScope) {
+ return{
+ restrict: 'ECA',
+ scope: {
+ title: '@title',
+ buttonid:'=buttonid',
+ footertext: '=footertext',
+ closelabel: '=closelabel'
+ },
+ transclude: true,
+ templateUrl: 'dialogs/modal.html',
+ replace: true,
+ link: function linkFn(scope, lElement, attrs, parentCtrl) {
+ scope.title = attrs.title;
+ scope.footertext = attrs.footertext;
+ scope.closelabel = attrs.closelabel;
+ scope.close = attrs.close;
+ scope.extrabutton = attrs.extrabutton;
+ scope.extrabuttonlabel = attrs.extrabuttonlabel;
+ scope.buttonId = attrs.buttonid;
+
+
+ scope.closeDelegate = function(attr){
+ //always call method in parent controller scope
+ scope.$parent[attr](attrs.id,scope)
+ }
+
+ scope.extraDelegate = function(attr){
+ if(scope.dialogForm.$valid){
+ console.log(parentCtrl);
+ //always call method in parent controller scope
+ scope.$parent[attr](attrs.id)
+ }else{
+ $rootScope.$broadcast('alert','error','Please check your form input and resubmit.')
+ }
+ }
+ }
+ }
+}]);
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/dialogs/modal.html
----------------------------------------------------------------------
diff --git a/portal/js/dialogs/modal.html b/portal/js/dialogs/modal.html
new file mode 100644
index 0000000..6dcdaaa
--- /dev/null
+++ b/portal/js/dialogs/modal.html
@@ -0,0 +1,16 @@
+ <div class="modal show fade" tabindex="-1" role="dialog" aria-hidden="true">
+ <form ng-submit="extraDelegate(extrabutton)" name="dialogForm" novalidate>
+
+ <div class="modal-header">
+ <h1 class="title">{{title}}</h1>
+ </div>
+
+ <div class="modal-body" ng-transclude></div>
+ <div class="modal-footer">
+ {{footertext}}
+ <input type="submit" class="btn" id="dialogButton-{{buttonId}}" ng-if="extrabutton" ng-disabled="!dialogForm.$valid" aria-hidden="true" ng-value="extrabuttonlabel"/>
+ <button class="btn cancel pull-left" data-dismiss="modal" aria-hidden="true"
+ ng-click="closeDelegate(close)">{{closelabel}}
+ </button>
+ </div>
+ </form> </div>
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/alert-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/global/alert-controller.js b/portal/js/global/alert-controller.js
new file mode 100644
index 0000000..6afb01f
--- /dev/null
+++ b/portal/js/global/alert-controller.js
@@ -0,0 +1,33 @@
+'use strict'
+
+AppServices.Controllers.controller('AlertCtrl', ['$scope', '$rootScope', '$timeout',
+ function ($scope, $rootScope, $timeout) {
+
+ $scope.alertDisplay = 'none';
+ $scope.alerts = [];
+
+ //alert types error,success,warning,info
+
+ $scope.$on('alert', function(event, type, message, permanent) {
+ $scope.addAlert(type, message, permanent);
+ });
+
+ $scope.$on('clear-alerts', function(event, message) {
+ $scope.alerts = [];
+ });
+
+ $scope.addAlert = function(type, message, permanent) {
+ $scope.alertDisplay = 'block';
+ $scope.alerts.push({type:type, msg:message});
+ $scope.applyScope();
+ if(!permanent){
+ $timeout(function(){$scope.alerts.shift()},5000);
+ }
+ };
+
+ $scope.closeAlert = function(index) {
+ $scope.alerts.splice(index, 1);
+ };
+
+
+ }]);
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/alert-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/global/alert-directive.js b/portal/js/global/alert-directive.js
new file mode 100644
index 0000000..1baff4b
--- /dev/null
+++ b/portal/js/global/alert-directive.js
@@ -0,0 +1,40 @@
+'use strict';
+
+AppServices.Directives.directive('alerti', ["$rootScope","$timeout", function ($rootScope,$timeout) {
+ return{
+ restrict: 'ECA',
+ scope: {
+ type: '=type',
+ closeable: '@closeable',
+ index: '&index'
+ },
+ template: '<div class="alert" ng-class="type && \'alert-\' + type">' +
+ ' <button ng-show="closeable" type="button" class="close" ng-click="closeAlert(index)">×</button>' +
+ ' <i ng-if="type === \'warning\'" class="pictogram pull-left" style="font-size:3em;line-height:0.4">💥</i>' +
+ ' <i ng-if="type === \'info\'" class="pictogram pull-left">ℹ</i>' +
+ ' <i ng-if="type === \'error\'" class="pictogram pull-left">⚡</i>' +
+ ' <i ng-if="type === \'success\'" class="pictogram pull-left">👍</i>' +
+ '<div ng-transclude></div>' +
+ '</div>',
+ replace: true,
+ transclude: true,
+ link: function linkFn(scope, lElement, attrs) {
+
+// if($rootScope.alertClick){
+
+ $timeout(function(){lElement.addClass('fade-out')},4000)
+
+ lElement.click(function(){
+
+ if(attrs.index){
+ scope.$parent.closeAlert(attrs.index);
+ }
+
+ });
+
+// }
+ //need to do a 10ms timeout to apply animation :(
+ setTimeout(function(){lElement.addClass('alert-animate');},10)
+ }
+ }
+}])
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/app-switcher-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/global/app-switcher-directive.js b/portal/js/global/app-switcher-directive.js
new file mode 100644
index 0000000..56cc77b
--- /dev/null
+++ b/portal/js/global/app-switcher-directive.js
@@ -0,0 +1,35 @@
+'use strict';
+
+AppServices.Directives.directive('appswitcher', ["$rootScope", function ($rootScope) {
+ return{
+ restrict: 'ECA',
+ scope: "=",
+ templateUrl: 'global/appswitcher-template.html',
+ replace: true,
+ transclude: true,
+ link: function linkFn(scope, lElement, attrs) {
+
+ var classNameOpen = 'open';
+ $('ul.nav li.dropdownContainingSubmenu').hover(
+ function () {
+ $(this).addClass(classNameOpen);
+ },
+ function () {
+ $(this).removeClass(classNameOpen);
+ }
+ );
+ $('#globalNav > a').mouseover(globalNavDetail);
+ $('#globalNavDetail').mouseover(globalNavDetail);
+ $('#globalNavSubmenuContainer ul li').mouseover(
+ function () {
+ $('#globalNavDetail > div').removeClass(classNameOpen);
+ $('#' + this.getAttribute('data-globalNavDetail')).addClass(classNameOpen);
+ }
+ );
+ function globalNavDetail() {
+ $('#globalNavDetail > div').removeClass(classNameOpen);
+ $('#globalNavDetailApiPlatform').addClass(classNameOpen);
+ }
+ }
+ }
+}])
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/appswitcher-template.html
----------------------------------------------------------------------
diff --git a/portal/js/global/appswitcher-template.html b/portal/js/global/appswitcher-template.html
new file mode 100644
index 0000000..8bdd6aa
--- /dev/null
+++ b/portal/js/global/appswitcher-template.html
@@ -0,0 +1,34 @@
+<li id="globalNav" class="dropdown dropdownContainingSubmenu active">
+ <a class="dropdown-toggle" data-toggle="dropdown">API Platform<b class="caret"></b></a>
+ <ul class="dropdown-menu pull-right">
+ <li id="globalNavSubmenuContainer">
+ <ul>
+ <li data-globalNavDetail="globalNavDetailApigeeHome"><a target="_blank" href="http://apigee.com">Apigee Home</a></li>
+ <li data-globalNavDetail="globalNavDetailAppServices" class="active"><a target="_blank" href="https://apigee.com/usergrid/">App Services</a></li>
+ <li data-globalNavDetail="globalNavDetailApiPlatform" ><a target="_blank" href="https://enterprise.apigee.com">API Platform</a></li>
+ <li data-globalNavDetail="globalNavDetailApiConsoles"><a target="_blank" href="http://apigee.com/providers">API Consoles</a></li>
+ </ul>
+ </li>
+ <li id="globalNavDetail">
+ <div id="globalNavDetailApigeeHome">
+ <div class="globalNavDetailApigeeLogo"></div>
+ <div class="globalNavDetailDescription">You need apps and apps need APIs. Apigee is the leading API platform for enterprises and developers.</div>
+ </div>
+ <div id="globalNavDetailAppServices">
+ <div class="globalNavDetailSubtitle">For App Developers</div>
+ <div class="globalNavDetailTitle">App Services</div>
+ <div class="globalNavDetailDescription">Build engaging applications, store data, manage application users, and more.</div>
+ </div>
+ <div id="globalNavDetailApiPlatform">
+ <div class="globalNavDetailSubtitle">For API Developers</div>
+ <div class="globalNavDetailTitle">API Platform</div>
+ <div class="globalNavDetailDescription">Create, configure, manage and analyze your APIs and resources.</div>
+ </div>
+ <div id="globalNavDetailApiConsoles">
+ <div class="globalNavDetailSubtitle">For API Developers</div>
+ <div class="globalNavDetailTitle">API Consoles</div>
+ <div class="globalNavDetailDescription">Explore over 100 APIs with the Apigee API Console, or create and embed your own API Console.</div>
+ </div>
+ </li>
+ </ul>
+</li>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/insecure-banner.html
----------------------------------------------------------------------
diff --git a/portal/js/global/insecure-banner.html b/portal/js/global/insecure-banner.html
new file mode 100644
index 0000000..78f90c7
--- /dev/null
+++ b/portal/js/global/insecure-banner.html
@@ -0,0 +1,7 @@
+<div ng-if="securityWarning" ng-cloak class="demo-holder">
+ <div class="alert alert-demo alert-animate">
+ <div class="alert-text">
+ <i class="pictogram">⚠</i>Warning: This application has "sandbox" permissions and is not production ready. <a target="_blank" href="http://apigee.com/docs/app-services/content/securing-your-app">Please go to our security documentation to find out more.</a></span>
+ </div>
+ </div>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/insecure-banner.js
----------------------------------------------------------------------
diff --git a/portal/js/global/insecure-banner.js b/portal/js/global/insecure-banner.js
new file mode 100644
index 0000000..f4859e5
--- /dev/null
+++ b/portal/js/global/insecure-banner.js
@@ -0,0 +1,46 @@
+
+AppServices.Directives.directive('insecureBanner', ['$rootScope','ug', function ($rootScope,ug) {
+ return{
+ restrict: 'E',
+ transclude: true,
+ templateUrl:'global/insecure-banner.html',
+ link: function linkFn(scope, lElement, attrs) {
+ scope.securityWarning = false;
+
+ scope.$on('roles-received',function(evt,roles){
+ scope.securityWarning = false;
+ if(!roles || !roles._list)
+ return;
+ roles._list.forEach(function(roleHolder){
+ var role = roleHolder._data;
+ if(role.name.toUpperCase()==='GUEST'){
+ roleHolder.getPermissions(function(err, data){
+ if (!err) {
+ if(roleHolder.permissions){
+ roleHolder.permissions.forEach(function(permission){
+ if(permission.path.indexOf('/**')>=0){
+ scope.securityWarning = true;
+ scope.applyScope();
+ }
+ });
+ }
+ }
+ });
+
+ }
+ });
+ });
+
+ var initialized = false;
+ scope.$on('app-initialized',function(){
+ !initialized && ug.getRoles();
+ initialized=true;
+ });
+
+ scope.$on('app-changed',function(){
+ scope.securityWarning = false;
+ ug.getRoles();
+ });
+ }
+ }
+}]);
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/page-constants.js
----------------------------------------------------------------------
diff --git a/portal/js/global/page-constants.js b/portal/js/global/page-constants.js
new file mode 100644
index 0000000..f77b384
--- /dev/null
+++ b/portal/js/global/page-constants.js
@@ -0,0 +1,11 @@
+'use strict'
+
+/**
+ * Defines application-wide key value pairs
+ */
+
+AppServices.Constants.constant('configuration', {
+
+ ITEMS_URL: 'global/temp.json'
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/page-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/global/page-controller.js b/portal/js/global/page-controller.js
new file mode 100644
index 0000000..81aafcb
--- /dev/null
+++ b/portal/js/global/page-controller.js
@@ -0,0 +1,392 @@
+'use strict'
+
+AppServices.Controllers.controller('PageCtrl',
+ [ 'data',
+ 'ug',
+ 'utility',
+ '$scope',
+ '$rootScope',
+ '$location',
+ '$routeParams',
+ '$q',
+ '$route',
+ '$log',
+ '$analytics', function (data,
+ ug,
+ utility,
+ $scope,
+ $rootScope,
+ $location,
+ $routeParams,
+ $q,
+ $route,
+ $log,
+ $analytics) {
+
+ var initScopeVariables = function(){
+ //$rootScope.urls()... will determine which URL should be used for a given environment
+ $scope.loadingText = 'Loading...';
+ $scope.use_sso = false;
+ $scope.newApp = {name: ''};
+ $scope.getPerm = '';
+ $scope.postPerm = '';
+ $scope.putPerm = '';
+ $scope.deletePerm = '';
+ $scope.usersTypeaheadValues = [];
+ $scope.groupsTypeaheadValues = [];
+ $scope.rolesTypeaheadValues = [];
+ $rootScope.sdkActive = false;
+ $rootScope.demoData = false;
+ $scope.queryStringApplied = false;
+ $rootScope.loaded = $rootScope.activeUI = false;
+ for (var key in Usergrid.regex) {
+ $scope[key] = Usergrid.regex[key];
+ }
+
+ $scope.options = Usergrid.options;
+
+ var getQuery = function () {
+ var result = {}, queryString = location.search.slice(1),
+ re = /([^&=]+)=([^&]*)/g, m;
+ while (m = re.exec(queryString)) {
+ result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
+ }
+ return result;
+ };
+ $scope.queryString = getQuery();
+ };
+
+ initScopeVariables();
+
+ $rootScope.urls = function(){
+ var BASE_URL = '';
+ var DATA_URL = '';
+ var qs = $location.search();
+
+ switch(true){
+ case $location.host() === 'appservices.apigee.com' && location.pathname.indexOf('/dit') >= 0 :
+ //DIT
+ BASE_URL = 'https://accounts.jupiter.apigee.net';
+ DATA_URL = 'http://apigee-internal-prod.jupiter.apigee.net';
+ $scope.use_sso = true;
+ break;
+ case $location.host() === 'appservices.apigee.com' && location.pathname.indexOf('/mars') >= 0 :
+ //staging
+ BASE_URL = 'https://accounts.mars.apigee.net';
+ DATA_URL = 'http://apigee-internal-prod.mars.apigee.net';
+ $scope.use_sso = true;
+ break;
+ case $location.host() === 'appservices.apigee.com' :
+ //enterprise portals
+ DATA_URL = Usergrid.overrideUrl;
+ break;
+ case $location.host() === 'apigee.com' :
+ //prod
+ BASE_URL = 'https://accounts.apigee.com';
+ DATA_URL = 'https://api.usergrid.com';
+ $scope.use_sso = true;
+ break;
+ case $location.host() === 'usergrid.dev':
+ //development
+ DATA_URL = 'https://api.usergrid.com';
+ break;
+ default :
+ DATA_URL = Usergrid.overrideUrl;
+ break;
+ }
+ //override with querystring
+ DATA_URL = qs.api_url || DATA_URL;
+ $scope.apiUrl = DATA_URL = DATA_URL.lastIndexOf('/') === DATA_URL.length - 1 ? DATA_URL.substring(0,DATA_URL.length-1) : DATA_URL;
+
+ return {
+ DATA_URL: DATA_URL,
+ LOGIN_URL: BASE_URL + '/accounts/sign_in',
+ PROFILE_URL: BASE_URL + '/accounts/my_account',
+ LOGOUT_URL: BASE_URL + '/accounts/sign_out'
+ }
+ }
+
+ //used in users
+ $rootScope.gotoPage = function(path){
+ $location.path(path);
+ }
+
+ var notRegistration = function(){
+ return "/forgot-password"!==$location.path() && "/register"!==$location.path();
+ };
+
+ //called in ng-init on main index page (first method called at app startup and every main navigation)
+ var verifyUser = function(){
+ //avoid polluting our target route with login path
+ if ($location.path().slice(0, '/login'.length) !== '/login') {
+ $rootScope.currentPath = $location.path();
+ //show loading screen during verify process
+ // $location.path('/login/loading');
+ }
+
+ //first check to see if there is a token in the query string, if so, save it
+ if ($routeParams.access_token && $routeParams.admin_email && $routeParams.uuid) {
+ ug.set('token', $routeParams.access_token);
+ ug.set('email', $routeParams.admin_email);
+ ug.set('uuid', $routeParams.uuid);
+ var clean_uri = location.protocol + "//" + location.host + location.pathname;
+ window.history.replaceState({}, document.title, clean_uri);
+ }
+
+ $scope.deferredLogin = $q.defer();
+ //use a promise so we can update afterwards in other UI areas
+ //next try to quick login
+ ug.checkAuthentication();
+ };
+
+
+ $scope.profile = function(){
+ if($scope.use_sso){
+ window.location = $rootScope.urls().PROFILE_URL + '?callback=' + encodeURIComponent($location.absUrl());
+ }else{
+ $location.path('/profile')
+ }
+
+ };
+
+ $scope.showModal = function(id){
+ $('#' + id).modal('show')
+ };
+
+ $scope.hideModal = function(id){
+ $('#' + id).modal('hide')
+ };
+
+
+ //adding this here for now
+ //todo - create a controller for usergrid functionality where all UG pages inherit
+ $scope.deleteEntities = function(collection, successBroadcast, errorMessage) {
+ collection.resetEntityPointer();
+ var entitiesToDelete = []
+ while(collection.hasNextEntity()) {
+ var entity = collection.getNextEntity();
+ var checked = entity.checked;
+ if(checked){
+ entitiesToDelete.push(entity);
+ }
+ }
+ var count = 0,success=false;
+ for(var i=0; i<entitiesToDelete.length; i++) {
+ var entity = entitiesToDelete[i];
+ collection.destroyEntity(entity, function(err){
+ count++;
+ if(err){
+ $rootScope.$broadcast('alert', 'error', errorMessage);
+ $rootScope.$broadcast(successBroadcast+'-error',err);
+ }else{
+ success=true;
+ }
+
+ if(count===entitiesToDelete.length){
+ success && $rootScope.$broadcast(successBroadcast);
+ $scope.applyScope();
+ }
+ });
+ }
+ };
+
+ $scope.selectAllEntities = function(list,that,varName,setValue){
+ varName = varName || 'master';
+ var val = that[varName] ;
+ if(setValue == undefined){
+ setValue = true;
+ }
+
+ if(setValue){
+ that[varName] = val = !val;
+ }
+ list.forEach(function(entitiy){
+ entitiy.checked = val;
+ });
+ };
+
+
+ $scope.createPermission = function(type, entity, path, permissions) {
+ //e.g.: "get,post,put:/mypermission"
+// var path = $scope.path;
+
+ if(path.charAt(0) != '/') {
+ path = '/'+path;
+ }
+ var ops = "";
+ var s = "";
+ if (permissions.getPerm) {
+ ops = "get";
+ s = ",";
+ }
+ if (permissions.postPerm) {
+ ops = ops + s + "post";
+ s = ",";
+ }
+ if (permissions.putPerm) {
+ ops = ops + s + "put";
+ s = ",";
+ }
+ if (permissions.deletePerm) {
+ ops = ops + s + "delete";
+ s = ",";
+ }
+ var permission = ops + ":" + path;
+
+ return permission
+ };
+
+ $scope.formatDate = function(date){
+ return new Date(date).toUTCString();
+ };
+
+ $scope.clearCheckbox = function(id){
+ if($('#'+id).attr('checked')){
+ $('#'+id).click();
+ }
+ };
+
+ $scope.removeFirstSlash = function(path){
+ return path.indexOf('/') === 0 ? path.substring(1,path.length): path;
+ };
+
+ $scope.applyScope = function(cb){
+ cb = typeof cb === 'function' ? cb : function(){};
+ if(!this.$$phase) {
+ return this.$apply(cb);
+ }else{
+ cb();
+ }
+ }
+
+ $scope.valueSelected = function(list){
+ return list && (list.some(function(item){return item.checked;}));
+ };
+
+ $scope.$on('users-typeahead-received', function(event, users) {
+ $scope.usersTypeaheadValues = users;
+ if(!$scope.$$phase) {
+ $scope.$apply();
+ }
+ });
+ $scope.$on('groups-typeahead-received', function(event, groups) {
+ $scope.groupsTypeaheadValues = groups;
+ if(!$scope.$$phase) {
+ $scope.$apply();
+ }
+ });
+ $scope.$on('roles-typeahead-received', function(event, roles) {
+ $scope.rolesTypeaheadValues = roles;
+ if(!$scope.$$phase) {
+ $scope.$apply();
+ }
+ });
+
+ $scope.$on('checkAuthentication-success', function () {
+ //all is well - repopulate objects
+ $scope.loaded = true;
+ $rootScope.activeUI = true;
+ $scope.applyScope();
+
+
+ if(!$scope.queryStringApplied){
+ $scope.queryStringApplied = true;
+ setTimeout(function(){
+ //if querystring exists then operate on it.
+ if ($scope.queryString.org) {
+ $rootScope.$broadcast('change-org',$scope.queryString.org);
+ }
+ }, 1000)
+ }
+
+ $scope.deferredLogin && $scope.deferredLogin.resolve();
+ $rootScope.$broadcast('app-initialized');
+
+ //boolean defines is top level ui menus/buttons should be active/shown
+ });
+
+ $scope.$on('checkAuthentication-error', function (args,err, missingData,email) {
+ $scope.loaded = true;
+ if (err && !$scope.use_sso && notRegistration()) {
+ //there was an error on re-auth lite, immediately send to login
+ ug.logout();
+ $location.path('/login');
+ $scope.applyScope();
+ } else {
+ if (missingData && notRegistration()) {
+ if (!email && $scope.use_sso) {
+ window.location = $rootScope.urls().LOGIN_URL + '?callback=' + encodeURIComponent($location.absUrl().split('?')[0]);
+ return;
+ }
+ ug.reAuthenticate(email);
+ }
+ }
+ });
+
+ $scope.$on('reAuthenticate-success', function (args,err, data, user, organizations, applications) {
+
+ //the user is authenticated
+ $rootScope.$broadcast('loginSuccesful', user, organizations, applications);
+ $rootScope.$emit('loginSuccesful', user, organizations, applications);
+ $rootScope.$broadcast('checkAuthentication-success');
+ $scope.applyScope(function () {
+ $scope.deferredLogin.resolve();
+ $location.path('/org-overview');
+ })
+ });
+
+ $scope.$on('reAuthenticate-error', function () {
+ //user is not authenticated, send to SSO if enabled
+ if ($scope.use_sso) {
+ //go to sso
+ window.location = $rootScope.urls().LOGIN_URL + '?callback=' + encodeURIComponent($location.absUrl().split('?')[0]);
+ } else {
+ //go to login page
+ if (notRegistration()) {
+ ug.logout();
+ $location.path('/login');
+ $scope.applyScope();
+ }
+ }
+ });
+
+ $scope.$on('loginSuccessful',function(){
+ $rootScope.activeUI = true;
+ });
+
+ $scope.$on('app-changed',function(args,oldVal,newVal,preventReload){
+ if(newVal!==oldVal && !preventReload){
+ $route.reload();
+ }
+ });
+
+ $scope.$on('org-changed',function(args, oldOrg,newOrg){
+ ug.getApplications();
+ $route.reload();
+ });
+
+ $scope.$on('app-settings-received',function(evt,data){
+ });
+
+
+ //verify on every route change
+ $scope.$on('$routeChangeSuccess', function () {
+ //todo possibly do a date check here for token expiry
+ //so we don't call this on every nav change
+ verifyUser();
+ $scope.showDemoBar = $location.path().slice(0,'/performance'.length) === '/performance';
+ if(!$scope.showDemoBar){
+ $rootScope.demoData = false;
+ }
+ $analytics.pageTrack($location.path());
+ });
+ $scope.$on('applications-received', function (event, applications) {
+ $scope.applications = applications;
+ $scope.hasApplications = Object.keys(applications).length > 0;
+ });
+
+ //init app
+ ug.getAppSettings();
+
+ }]);
+
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/page-service.js
----------------------------------------------------------------------
diff --git a/portal/js/global/page-service.js b/portal/js/global/page-service.js
new file mode 100644
index 0000000..d1d08de
--- /dev/null
+++ b/portal/js/global/page-service.js
@@ -0,0 +1,214 @@
+AppServices.Services.factory('data', function (configuration, $q, $http, $resource, $rootScope, $log, $analytics) {
+
+ function reportError(data,config){
+ try {
+ $analytics.eventTrack('error', {
+ category: 'App Services', label: data + ':' + config.url + ':' + (sessionStorage['apigee_uuid'] || 'na')
+ });
+ } catch (e) {
+ console.log(e)
+ }
+ };
+ var getAccessToken = function(){
+ return sessionStorage.getItem('accessToken');
+ };
+
+ return {
+
+ /**
+ * Retrieves page data
+ * @param {string} id the name of the single page item to get (a key in the JSON) can be null.
+ * @param {string} url location of the file/endpoint.
+ * @return {Promise} Resolves to JSON.
+ */
+ get: function (id, url) {
+ var items, deferred;
+
+ deferred = $q.defer();
+
+ $http.get((url || configuration.ITEMS_URL)).
+ success(function (data, status, headers, config) {
+ var result;
+ if (id) {
+ angular.forEach(data, function (obj, index) {
+ if (obj.id === id) {
+ result = obj;
+ }
+ });
+ } else {
+ result = data;
+ }
+ deferred.resolve(result);
+ }).
+ error(function (data, status, headers, config) {
+ $log.error(data, status, headers, config);
+ reportError(data,config);
+ deferred.reject(data);
+ });
+
+ return deferred.promise;
+ },
+
+// getLocal: function (url) {
+// var retdata;
+// $http.get(url).
+// success(function (data, status, headers, config) {
+// retdata = data;
+// }).
+// error(function (data, status, headers, config) {
+// $log.error(data, status, headers, config);
+// });
+//
+// return retdata;
+// },
+
+ /**
+ * Retrieves page data via jsonp
+ * @param {string} url the location of the JSON/RESTful endpoint.
+ * @param {string} successCallback function called on success.
+ */
+// jsonp: function (url,successCallback) {
+// var self = this;
+// $http.jsonp(url).
+// success(function(data, status, headers, config) {
+// successCallback(data,status,headers,config);
+// }).
+// error(function(data, status, headers, config) {
+// console.log("ERROR: Could not get data. " + url);
+// });
+// },
+
+ jsonp: function (objectType,criteriaId,params,successCallback) {
+ if(!params){
+ params = {};
+ }
+
+ params.demoApp = $rootScope.demoData;
+ params.access_token = getAccessToken();
+ params.callback = 'JSON_CALLBACK';
+
+
+ $rootScope.$broadcast("ajax_loading", objectType);
+
+ $http.jsonp($rootScope.urls().DATA_URL + '/' + $rootScope.currentOrg + '/' + $rootScope.currentApp + '/apm/' + objectType + '/' + criteriaId,{params:params,headers:{}}).
+ success(function(data, status, headers, config) {
+ successCallback(data,status,headers,config);
+
+ $rootScope.$broadcast("ajax_finished", objectType);
+
+
+ }).
+ error(function(data, status, headers, config) {
+ $log.error("ERROR: Could not get jsonp data. " + $rootScope.urls().DATA_URL + objectType + '/' + criteriaId);
+ reportError(data,config);
+ });
+ },
+
+ jsonp_simple: function (objectType,appId,params) {
+ if(!params){
+ params = {};
+ }
+
+ params.access_token = getAccessToken();
+ params.callback = 'JSON_CALLBACK';
+
+
+ var deferred = $q.defer();
+
+ $http.jsonp($rootScope.urls().DATA_URL + '/' + $rootScope.currentOrg + '/' + $rootScope.currentApp + '/apm/' + objectType + "/" + appId,{params:params}).
+ success(function(data, status, headers, config) {
+ deferred.resolve(data);
+ }).
+ error(function(data, status, headers, config) {
+ $log.error("ERROR: Could not get jsonp data. " + $rootScope.urls().DATA_URL + '/' + $rootScope.currentOrg + '/' + $rootScope.currentApp + '/apm/' + objectType + "/" + appId);
+ reportError(data,config);
+ deferred.reject(data);
+ });
+
+ return deferred.promise;
+ },
+
+ jsonp_raw: function (objectType,appId,params) {
+ if(!params){
+ params = {};
+ }
+
+ params.access_token = getAccessToken();
+ params.callback = 'JSON_CALLBACK';
+
+
+ var deferred = $q.defer();
+
+ $http.jsonp($rootScope.urls().DATA_URL + '/' + $rootScope.currentOrg + '/' + $rootScope.currentApp + '/' + objectType,{params:params}).
+ success(function(data, status, headers, config) {
+ deferred.resolve(data);
+ }).
+ error(function(data, status, headers, config) {
+ $log.error("ERROR: Could not get jsonp data. " + $rootScope.urls().DATA_URL + objectType + '/' + appId);
+ reportError(data,config);
+ deferred.reject(data);
+ });
+
+ return deferred.promise;
+ },
+
+ resource: function(params,isArray) {
+ //temporary url for REST endpoints
+
+ return $resource($rootScope.urls().DATA_URL + '/:orgname/:appname/:username/:endpoint',
+ {
+
+ },
+ {
+ get: {
+ method:'JSONP',
+ isArray: isArray,
+ params: params
+ },
+ login: {
+ method:'GET',
+ url: $rootScope.urls().DATA_URL + '/management/token',
+ isArray: false,
+ params: params
+ },
+ save: {
+ url: $rootScope.urls().DATA_URL + '/' + params.orgname + '/' + params.appname,
+ method:'PUT',
+ isArray: false,
+ params: params
+ }
+ });
+ },
+
+ post: function(url,callback,payload,headers){
+
+ var accessToken = getAccessToken();
+
+ if(payload){
+ payload.access_token = accessToken;
+ }else{
+ payload = {access_token:accessToken}
+ }
+
+ if(!headers){
+ headers = {Bearer:accessToken};
+ }
+
+ $http({method: 'POST', url: url, data: payload, headers: headers}).
+ success(function(data, status, headers, config) {
+ callback(data)
+ }).
+ error(function(data, status, headers, config) {
+ reportError(data,config);
+ callback(data)
+ });
+
+ }
+
+
+
+ }
+
+
+});
+
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/temp.json
----------------------------------------------------------------------
diff --git a/portal/js/global/temp.json b/portal/js/global/temp.json
new file mode 100644
index 0000000..f8c400a
--- /dev/null
+++ b/portal/js/global/temp.json
@@ -0,0 +1,18 @@
+[
+ {
+ "id": "appOverview",
+ "top5Crashes": {
+
+ "testdata": 123
+
+ }
+ },
+ {
+ "id": "errorsCrashes",
+ "top5Errors": {
+
+ "testdata": 456
+
+ }
+ }
+]
\ No newline at end of file