Added admin ui from IdentityBroker
|
@ -25,6 +25,11 @@
|
|||
<artifactId>keycloak-social</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
|
|
31
ui/pom.xml
Executable file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-ui</artifactId>
|
||||
<name>Keycloak UI</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,314 @@
|
|||
/* General styles */
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
|
||||
#actions-bg {width: 1150px;}
|
||||
|
||||
#container-right-bg {
|
||||
margin-left: 300px;
|
||||
width: 900px;
|
||||
}
|
||||
|
||||
.span9 {
|
||||
width: 840px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.initial {margin-left: 160px;}
|
||||
|
||||
.form-mobile-redirect .header-mobile-redirect .control-group,
|
||||
.form-mobile-redirect .header-mobile-redirect .control-group:last-child {
|
||||
width: 300px;
|
||||
margin-left: 60px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.header-mobile-redirect input, .header-mobile-redirect select {width: 175px;}
|
||||
|
||||
tbody.sortable tr td:nth-child(1) {width: 150px;}
|
||||
tbody.sortable tr td:nth-child(2) {width: 420px;}
|
||||
tbody.sortable tr td:nth-child(3v) {width: 130px;}
|
||||
|
||||
#mappings .input-medium {width: 180px;}
|
||||
|
||||
footer {width: 1170px;}
|
||||
|
||||
footer p {margin-left: 300px;}
|
||||
|
||||
footer > ul {margin-right: 25px;}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
|
||||
td.actions > a,
|
||||
td.actions > button,
|
||||
td.actions > div {visibility: hidden}
|
||||
|
||||
tr:hover > td.actions > a,
|
||||
tr:hover > td.actions > button,
|
||||
tr:hover > td.actions > div {visibility: visible;}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
|
||||
body {padding-top: 0;}
|
||||
|
||||
/* Topbar */
|
||||
|
||||
.navbar-fixed-top {
|
||||
margin-bottom: 2px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.navbar-fixed-top .navbar-inner {padding: 0;}
|
||||
|
||||
.navbar .container {width: 724px;}
|
||||
|
||||
.navbar .brand {margin-left: -20px;}
|
||||
|
||||
.nav-collapse {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.nav-collapse .nav {margin-bottom: 0;}
|
||||
|
||||
.navbar .nav-collapse .nav.pull-right {float: right;}
|
||||
|
||||
.nav-collapse .nav > li {float: left;}
|
||||
|
||||
.nav-collapse .nav > li > a {
|
||||
font-weight: normal;
|
||||
padding: 9px 10px 11px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Topbar dropdown */
|
||||
|
||||
.navbar .dropdown-menu {display: none;}
|
||||
|
||||
.open .dropdown-menu,
|
||||
.open .dropdown-menu:before,
|
||||
.open .dropdown-menu:after {display: block;}
|
||||
|
||||
.open .dropdown-menu {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px 5px 5px 5px;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 1px 0 0;
|
||||
padding: 4px 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.nav-collapse .dropdown-menu a {
|
||||
color: #333333;
|
||||
line-height: 18px;
|
||||
padding: 3px 15px;
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.nav-collapse .dropdown-menu li > a:hover,
|
||||
.nav-collapse .dropdown-menu .active > a,
|
||||
.nav-collapse .dropdown-menu .active > a:hover {
|
||||
background-color: #0088CC;
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Aside */
|
||||
|
||||
.nav-list a#add-page {top: 15px;}
|
||||
|
||||
#actions-bg {width: 730px;}
|
||||
|
||||
/* Content */
|
||||
|
||||
.form-horizontal .control-label {width: 125px;}
|
||||
|
||||
.form-horizontal .controls {margin-left: 140px;}
|
||||
|
||||
#container-right-bg {
|
||||
margin-left: 186px;
|
||||
width: 558px;
|
||||
}
|
||||
|
||||
.span9 {
|
||||
width: 518px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.initial {margin-left: 0;}
|
||||
|
||||
.header-mobile-redirect .control-group label {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-mobile-redirect .header-mobile-redirect .control-group {width: 300px;}
|
||||
|
||||
.form-mobile-redirect .header-mobile-redirect .control-group:last-child {
|
||||
margin-left: 87px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#mappings .input-medium {width: 85px;}
|
||||
|
||||
#space-group .top-actions .pull-right {
|
||||
float: none;
|
||||
clear: both;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.window-tree {width: 356px;}
|
||||
|
||||
/* Footer */
|
||||
|
||||
footer {
|
||||
width: 724px;
|
||||
height: 50px;
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin-left: 186px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
footer > ul {
|
||||
margin-left: 173px;
|
||||
float: none;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.navbar-fixed-top, .navbar-fixed-bottom {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
|
||||
.navbar-fixed-top {
|
||||
position: absolute;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.navbar-fixed-top .navbar-inner {padding: 0;}
|
||||
|
||||
.nav-collapse {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.nav-collapse .nav {margin-bottom: 0;}
|
||||
|
||||
.navbar .nav-collapse .nav.pull-right {float: right;}
|
||||
|
||||
.navbar #settings {display: none;}
|
||||
|
||||
.nav-collapse .nav > li {float: left;}
|
||||
|
||||
.nav-collapse .nav > li > a {
|
||||
font-weight: normal;
|
||||
padding: 9px 10px 11px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Topbar dropdown */
|
||||
|
||||
.navbar .dropdown-menu {display: none;}
|
||||
|
||||
.open .dropdown-menu,
|
||||
.open .dropdown-menu:before,
|
||||
.open .dropdown-menu:after {display: block;}
|
||||
|
||||
.open .dropdown-menu {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px 5px 5px 5px;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 1px 0 0;
|
||||
padding: 4px 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.nav-collapse .dropdown-menu a {
|
||||
color: #333333;
|
||||
line-height: 18px;
|
||||
padding: 3px 15px;
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.nav-collapse .dropdown-menu li > a:hover,
|
||||
.nav-collapse .dropdown-menu .active > a,
|
||||
.nav-collapse .dropdown-menu .active > a:hover {
|
||||
background-color: #0088CC;
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
#wrapper {margin-top: 40px;}
|
||||
|
||||
aside.span3,
|
||||
#actions-bg,
|
||||
#container-right-bg {display: none;}
|
||||
|
||||
.row {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
#container-right {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
padding-bottom: 74px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.span9 {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input-large,
|
||||
.input-xlarge,
|
||||
.input-xxlarge,
|
||||
input[class*="span"],
|
||||
select[class*="span"],
|
||||
textarea[class*="span"],
|
||||
.uneditable-input {width: 80%;}
|
||||
|
||||
footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
footer > ul,
|
||||
footer >p {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
footer ul li:first-child {
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
1266
ui/src/main/resources/META-INF/resources/css/admin.css
Normal file
17
ui/src/main/resources/META-INF/resources/css/styles.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.margin-top {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
div#httpProviderError {
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
div#loading {
|
||||
color: #868686;
|
||||
font-size: 14px;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
bottom: 5px;
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/body-bg.jpg
Normal file
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 999 B |
After Width: | Height: | Size: 935 B |
BIN
ui/src/main/resources/META-INF/resources/img/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/icon-close.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/icon-grippy.png
Normal file
After Width: | Height: | Size: 80 B |
BIN
ui/src/main/resources/META-INF/resources/img/icon-ok.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/sprite-icons-30.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
ui/src/main/resources/META-INF/resources/img/sprite-icons-30.psd
Normal file
BIN
ui/src/main/resources/META-INF/resources/img/sprite-three.psd
Normal file
49
ui/src/main/resources/META-INF/resources/index.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!doctype html>
|
||||
<html lang="en" ng-app="eventjugglerAdmin">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>EventJuggler Services Admin</title>
|
||||
|
||||
<link href="lib/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="lib/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="css/admin.css" rel="stylesheet">
|
||||
<link href="css/admin-responsive.css" rel="stylesheet">
|
||||
|
||||
<link href="css/styles.css" rel="stylesheet">
|
||||
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
<script src="lib/angular/ui-bootstrap-tpls-0.3.0.js"></script>
|
||||
|
||||
<script src="lib/jquery/jquery-1.9.0.js"></script>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
</head>
|
||||
|
||||
<body ng-controller="GlobalCtrl">
|
||||
|
||||
<div class="alert-container" ng-show="notification" ng-click="notification = null">
|
||||
<div class="alert alert-{{notification.type}}">{{notification.message}}</div>
|
||||
</div>
|
||||
|
||||
<div id="wrap">
|
||||
<div ng-include src="'partials/menu.html'"></div>
|
||||
|
||||
<div ng-view id="view" ng-hide="httpProviderError"></div>
|
||||
|
||||
<div id="httpProviderError" ng-show="httpProviderError">
|
||||
<button class="btn btn-danger" ng-click="httpProviderError=null">
|
||||
<strong>Error</strong> {{httpProviderError}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="loading">
|
||||
<i class="icon-spinner icon-spin"></i> Loading...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
153
ui/src/main/resources/META-INF/resources/js/app.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
|
||||
var eventjugglerModule = angular.module('eventjugglerAdmin', [ 'eventjugglerAdminServices', 'ui.bootstrap' ]);
|
||||
var resourceRequests = 0;
|
||||
|
||||
eventjugglerModule.config([ '$routeProvider', function($routeProvider) {
|
||||
$routeProvider.when('/activities/events', {
|
||||
templateUrl : 'partials/activities-events.html',
|
||||
resolve : {
|
||||
events : function(ActivitiesEventsLoader) {
|
||||
return ActivitiesEventsLoader();
|
||||
}
|
||||
},
|
||||
controller : ActivitiesEventsCtrl
|
||||
}).when('/activities/pages', {
|
||||
templateUrl : 'partials/activities-pages.html',
|
||||
resolve : {
|
||||
statistics : function(ActivitiesStatisticsLoader) {
|
||||
return ActivitiesStatisticsLoader();
|
||||
}
|
||||
},
|
||||
controller : ActivitiesStatisticsCtrl
|
||||
}).when('/activities', {
|
||||
templateUrl : 'partials/activities-statistics.html',
|
||||
resolve : {
|
||||
statistics : function(ActivitiesStatisticsLoader) {
|
||||
return ActivitiesStatisticsLoader();
|
||||
}
|
||||
},
|
||||
controller : ActivitiesStatisticsCtrl
|
||||
}).when('/applications/:key', {
|
||||
templateUrl : 'partials/application-detail.html',
|
||||
resolve : {
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
realms : function(RealmListLoader) {
|
||||
return RealmListLoader();
|
||||
},
|
||||
providers : function(ProviderListLoader) {
|
||||
return ProviderListLoader();
|
||||
}
|
||||
},
|
||||
controller : ApplicationDetailCtrl
|
||||
}).when('/applications', {
|
||||
templateUrl : 'partials/application-list.html',
|
||||
resolve : {
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : ApplicationListCtrl
|
||||
}).when('/realms/:realmKey/users/:userId', {
|
||||
templateUrl : 'partials/user-detail.html',
|
||||
resolve : {
|
||||
realms : function(RealmListLoader) {
|
||||
return RealmListLoader();
|
||||
},
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
}
|
||||
},
|
||||
controller : UserDetailCtrl
|
||||
}).when('/realms/:realmKey/users', {
|
||||
templateUrl : 'partials/user-list.html',
|
||||
resolve : {
|
||||
realms : function(RealmListLoader) {
|
||||
return RealmListLoader();
|
||||
},
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
users : function(UserListLoader) {
|
||||
return UserListLoader();
|
||||
}
|
||||
},
|
||||
controller : UserListCtrl
|
||||
}).when('/realms/:realmKey', {
|
||||
templateUrl : 'partials/realm-detail.html',
|
||||
resolve : {
|
||||
realms : function(RealmListLoader) {
|
||||
return RealmListLoader();
|
||||
},
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : RealmDetailCtrl
|
||||
}).when('/realms', {
|
||||
templateUrl : 'partials/realm-list.html',
|
||||
resolve : {
|
||||
realms : function(RealmListLoader) {
|
||||
return RealmListLoader();
|
||||
}
|
||||
},
|
||||
controller : RealmListCtrl
|
||||
}).otherwise({
|
||||
templateUrl : 'partials/home.html'
|
||||
});
|
||||
} ]);
|
||||
|
||||
eventjugglerModule.config(function($httpProvider) {
|
||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
||||
|
||||
var spinnerFunction = function(data, headersGetter) {
|
||||
if (resourceRequests == 0) {
|
||||
$('#loading').show();
|
||||
}
|
||||
resourceRequests++;
|
||||
return data;
|
||||
};
|
||||
$httpProvider.defaults.transformRequest.push(spinnerFunction);
|
||||
|
||||
$httpProvider.responseInterceptors.push('spinnerInterceptor');
|
||||
|
||||
});
|
||||
|
||||
eventjugglerModule.factory('errorInterceptor', function($q, $window, $rootScope, $location) {
|
||||
return function(promise) {
|
||||
return promise.then(function(response) {
|
||||
$rootScope.httpProviderError = null;
|
||||
return response;
|
||||
}, function(response) {
|
||||
$rootScope.httpProviderError = response.status;
|
||||
return $q.reject(response);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerModule.factory('spinnerInterceptor', function($q, $window, $rootScope, $location) {
|
||||
return function(promise) {
|
||||
return promise.then(function(response) {
|
||||
resourceRequests--;
|
||||
if (resourceRequests == 0) {
|
||||
$('#loading').hide();
|
||||
}
|
||||
return response;
|
||||
}, function(response) {
|
||||
resourceRequests--;
|
||||
if (resourceRequests == 0) {
|
||||
$('#loading').hide();
|
||||
}
|
||||
|
||||
return $q.reject(response);
|
||||
});
|
||||
};
|
||||
});
|
314
ui/src/main/resources/META-INF/resources/js/controllers.js
Normal file
|
@ -0,0 +1,314 @@
|
|||
'use strict';
|
||||
|
||||
function GlobalCtrl($scope, Auth, $location, Notifications) {
|
||||
|
||||
$scope.addMessage = function() {
|
||||
Notifications.success("test");
|
||||
};
|
||||
|
||||
$scope.auth = Auth;
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
}
|
||||
|
||||
function ActivitiesEventsCtrl($scope, events) {
|
||||
$scope.events = events;
|
||||
}
|
||||
|
||||
function ActivitiesStatisticsCtrl($scope, statistics) {
|
||||
$scope.statistics = statistics;
|
||||
}
|
||||
|
||||
function ApplicationListCtrl($scope, applications) {
|
||||
$scope.applications = applications;
|
||||
}
|
||||
|
||||
function ApplicationDetailCtrl($scope, applications, application, Application, realms, providers, $location, $window, $dialog, Notifications) {
|
||||
$scope.application = angular.copy(application);
|
||||
$scope.applications = applications;
|
||||
$scope.realms = realms;
|
||||
$scope.providers = providers;
|
||||
|
||||
$scope.callbackUrl = $window.location.origin + "/ejs-identity/api/callback/" + application.key;
|
||||
|
||||
$scope.create = !application.key;
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.$watch('application', function() {
|
||||
if (!angular.equals($scope.application, application)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.applicationForm.$valid) {
|
||||
if (!$scope.application.key) {
|
||||
Application.save($scope.application, function(data, headers) {
|
||||
var l = headers().location;
|
||||
var key = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/applications/" + key);
|
||||
Notifications.success("Created application");
|
||||
});
|
||||
} else {
|
||||
Application.update($scope.application, function() {
|
||||
$scope.changed = false;
|
||||
application = angular.copy($scope.application);
|
||||
if ($scope.create) {
|
||||
$location.url("/applications/" + $scope.application.key);
|
||||
}
|
||||
Notifications.success("Saved changes to the application");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$scope.applicationForm.showErrors = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.application = angular.copy(application);
|
||||
$scope.changed = false;
|
||||
$scope.applicationForm.showErrors = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/applications");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
var title = 'Delete ' + $scope.application.name;
|
||||
var msg = 'Are you sure you want to permanently delete this application?';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : 'Delete this application',
|
||||
cssClass : 'btn-primary'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
$scope.application.$remove(function() {
|
||||
$location.url("/applications");
|
||||
Notifications.success("Deleted application");
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.availableProviders = [];
|
||||
|
||||
$scope.addProvider = function() {
|
||||
if (!$scope.application.providers) {
|
||||
$scope.application.providers = [];
|
||||
}
|
||||
|
||||
$scope.application.providers.push({
|
||||
"providerId" : $scope.newProviderId
|
||||
});
|
||||
|
||||
$scope.newProviderId = null;
|
||||
};
|
||||
|
||||
$scope.getProviderDescription = function(providerId) {
|
||||
for ( var i = 0; i < $scope.providers.length; i++) {
|
||||
if ($scope.providers[i].id == providerId) {
|
||||
return $scope.providers[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeProvider = function(i) {
|
||||
$scope.application.providers.splice(i, 1);
|
||||
};
|
||||
|
||||
var updateAvailableProviders = function() {
|
||||
$scope.availableProviders.splice(0, $scope.availableProviders.length);
|
||||
|
||||
for ( var i in $scope.providers) {
|
||||
var add = true;
|
||||
|
||||
for ( var j in $scope.application.providers) {
|
||||
if ($scope.application.providers[j].providerId == $scope.providers[i].id) {
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (add) {
|
||||
$scope.availableProviders.push($scope.providers[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.openHelp = function(i) {
|
||||
$scope.providerHelpModal = true;
|
||||
$scope.providerHelp = {};
|
||||
$scope.providerHelp.index = i;
|
||||
$scope.providerHelp.description = $scope.getProviderDescription($scope.application.providers[i].providerId);
|
||||
};
|
||||
|
||||
$scope.closeHelp = function() {
|
||||
$scope.providerHelpModal = false;
|
||||
$scope.providerHelp = null;
|
||||
};
|
||||
|
||||
$scope.$watch("providers.length + application.providers.length", updateAvailableProviders);
|
||||
}
|
||||
|
||||
function RealmListCtrl($scope, realms) {
|
||||
$scope.realms = realms;
|
||||
}
|
||||
|
||||
function UserListCtrl($scope, realms, realm, users) {
|
||||
$scope.realms = realms;
|
||||
$scope.realm = realm;
|
||||
$scope.users = users;
|
||||
}
|
||||
|
||||
function UserDetailCtrl($scope, realms, realm, user, User, $location, $dialog, Notifications) {
|
||||
$scope.realms = realms;
|
||||
$scope.realm = realm;
|
||||
$scope.user = angular.copy(user);
|
||||
$scope.create = !user.userId;
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.$watch('user', function() {
|
||||
if (!angular.equals($scope.user, user)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.userForm.$valid) {
|
||||
User.save({
|
||||
realmKey : realm.key
|
||||
}, $scope.user, function() {
|
||||
$scope.changed = false;
|
||||
user = angular.copy($scope.user);
|
||||
|
||||
if ($scope.create) {
|
||||
$location.url("/realms/" + realm.key + "/users/" + user.userId);
|
||||
Notifications.success("Created user");
|
||||
} else {
|
||||
Notifications.success("Saved changes to user");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$scope.userForm.showErrors = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy(user);
|
||||
$scope.changed = false;
|
||||
$scope.userForm.showErrors = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.key + "/users");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
var title = 'Delete ' + $scope.user.userId;
|
||||
var msg = 'Are you sure you want to permanently delete this user?';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : 'Delete this user',
|
||||
cssClass : 'btn-primary'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
$scope.user.$remove({
|
||||
realmKey : realm.key,
|
||||
userId : $scope.user.userId
|
||||
}, function() {
|
||||
$location.url("/realms/" + realm.key + "/users");
|
||||
Notifications.success("Deleted user");
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function RealmDetailCtrl($scope, Realm, realms, realm, $location, $dialog, Notifications) {
|
||||
$scope.realms = realms;
|
||||
$scope.realm = angular.copy(realm);
|
||||
$scope.create = !realm.name;
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, realm)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.realmForm.$valid) {
|
||||
if (!$scope.realm.key) {
|
||||
Realm.save($scope.realm, function(data, headers) {
|
||||
var l = headers().location;
|
||||
var key = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + key);
|
||||
Notifications.success("Created realm");
|
||||
});
|
||||
} else {
|
||||
Realm.update($scope.realm, function() {
|
||||
$scope.changed = false;
|
||||
realm = angular.copy($scope.realm);
|
||||
if ($scope.create) {
|
||||
$location.url("/realms/" + $scope.realm.key);
|
||||
Notifications.success("Created realm");
|
||||
} else {
|
||||
Notifications.success("Saved changes to realm");
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$scope.realmForm.showErrors = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(realm);
|
||||
$scope.changed = false;
|
||||
$scope.realmForm.showErrors = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
var title = 'Delete ' + $scope.realm.name;
|
||||
var msg = 'Are you sure you want to permanently delete this realm?';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : 'Delete this realm',
|
||||
cssClass : 'btn-primary'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
Realm.remove($scope.realm, function() {
|
||||
$location.url("/realms");
|
||||
Notifications.success("Deleted realm");
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
276
ui/src/main/resources/META-INF/resources/js/services.js
Normal file
|
@ -0,0 +1,276 @@
|
|||
'use strict';
|
||||
|
||||
var eventjugglerServices = angular.module('eventjugglerAdminServices', [ 'ngResource' ]);
|
||||
|
||||
eventjugglerServices.factory('Notifications', function($rootScope, $timeout) {
|
||||
var notifications = {};
|
||||
|
||||
var scheduled = null;
|
||||
var schedulePop = function() {
|
||||
if (scheduled) {
|
||||
$timeout.cancel(scheduled);
|
||||
}
|
||||
|
||||
scheduled = $timeout(function() {
|
||||
$rootScope.notification = null;
|
||||
scheduled = null;
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
if (!$rootScope.notifications) {
|
||||
$rootScope.notifications = [];
|
||||
}
|
||||
|
||||
notifications.success = function(message) {
|
||||
$rootScope.notification = {
|
||||
type : "success",
|
||||
message : message
|
||||
};
|
||||
|
||||
schedulePop();
|
||||
};
|
||||
|
||||
return notifications;
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('Application', function($resource) {
|
||||
return $resource('/ejs-identity/api/admin/applications/:key', {
|
||||
key : '@key'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('ApplicationListLoader', function(Application, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
Application.query(function(applications) {
|
||||
delay.resolve(applications);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch applications');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('ApplicationLoader', function(Application, $route, $q) {
|
||||
return function() {
|
||||
var key = $route.current.params.key;
|
||||
if (key == 'new') {
|
||||
return {};
|
||||
} else {
|
||||
var delay = $q.defer();
|
||||
Application.get({
|
||||
key : key
|
||||
}, function(application) {
|
||||
delay.resolve(application);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch application ' + key);
|
||||
});
|
||||
return delay.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('Provider', function($resource) {
|
||||
return $resource('/ejs-identity/api/admin/providers');
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('ProviderListLoader', function(Provider, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
Provider.query(function(providers) {
|
||||
delay.resolve(providers);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch providers');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('Realm', function($resource) {
|
||||
return $resource('/ejs-identity/api/admin/realms/:key', {
|
||||
key : '@key'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('RealmListLoader', function(Realm, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
Realm.query(function(realms) {
|
||||
delay.resolve(realms);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch realms');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('RealmLoader', function(Realm, $route, $q) {
|
||||
return function() {
|
||||
var key = $route.current.params.realmKey;
|
||||
if (key == 'new') {
|
||||
return {};
|
||||
} else {
|
||||
var delay = $q.defer();
|
||||
Realm.get({
|
||||
key : key
|
||||
}, function(realm) {
|
||||
delay.resolve(realm);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch key ' + key);
|
||||
});
|
||||
return delay.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('User', function($resource) {
|
||||
return $resource('/ejs-identity/api/im/:realmKey/users/:userId', {
|
||||
realmKey : '@realmKey',
|
||||
userId : '@userId'
|
||||
}, {
|
||||
save : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('UserListLoader', function(User, $route, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
User.query({
|
||||
realmKey : $route.current.params.realmKey
|
||||
}, function(users) {
|
||||
delay.resolve(users);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch users');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('UserLoader', function(User, $route, $q) {
|
||||
return function() {
|
||||
var userId = $route.current.params.userId;
|
||||
if (userId == 'new') {
|
||||
return {};
|
||||
} else {
|
||||
var delay = $q.defer();
|
||||
User.get({
|
||||
realmKey : $route.current.params.realmKey,
|
||||
userId : userId
|
||||
}, function(user) {
|
||||
delay.resolve(user);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch user ' + $route.current.params.userId);
|
||||
});
|
||||
return delay.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('Activities', function($resource) {
|
||||
var activities = {};
|
||||
activities.events = $resource('/ejs-activities/api/events');
|
||||
activities.statistics = $resource('/ejs-activities/api/statistics');
|
||||
return activities;
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('ActivitiesStatisticsLoader', function(Activities, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
Activities.statistics.get(function(statistics) {
|
||||
delay.resolve(statistics);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch statistics');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.factory('ActivitiesEventsLoader', function(Activities, $q) {
|
||||
return function() {
|
||||
var delay = $q.defer();
|
||||
Activities.events.query({
|
||||
"max" : 10
|
||||
}, function(events) {
|
||||
delay.resolve(events);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch events');
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
});
|
||||
|
||||
eventjugglerServices.service('Auth', function($resource, $http, $location, $routeParams) {
|
||||
var auth = {};
|
||||
auth.user = {};
|
||||
|
||||
var parameters = window.location.search.substring(1).split("&");
|
||||
for ( var i = 0; i < parameters.length; i++) {
|
||||
var param = parameters[i].split("=");
|
||||
if (decodeURIComponent(param[0]) == "token") {
|
||||
auth.token = decodeURIComponent(param[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (auth.token) {
|
||||
$location.search("token", null);
|
||||
localStorage.setItem("token", auth.token);
|
||||
} else {
|
||||
auth.token = localStorage.getItem("token");
|
||||
}
|
||||
|
||||
if (auth.token) {
|
||||
$http.defaults.headers.common['token'] = auth.token;
|
||||
|
||||
auth.user = $resource('/ejs-identity/api/auth/userinfo').get({
|
||||
appKey : "system",
|
||||
token : auth.token
|
||||
}, function() {
|
||||
if (auth.user.userId) {
|
||||
auth.loggedIn = true;
|
||||
auth.root = auth.user.userId == "root";
|
||||
|
||||
var displayName;
|
||||
if (auth.user.firstName || auth.user.lastName) {
|
||||
displayName = auth.user.firstName;
|
||||
if (auth.user.lastName) {
|
||||
displayName = displayName ? displayName + " " + auth.user.lastName : auth.user.lastName;
|
||||
}
|
||||
} else {
|
||||
displayName = auth.user.userId;
|
||||
}
|
||||
|
||||
auth.user.displayName = displayName;
|
||||
} else {
|
||||
auth.logout();
|
||||
}
|
||||
}, function() {
|
||||
auth.logout();
|
||||
});
|
||||
}
|
||||
|
||||
auth.logout = function() {
|
||||
$resource('/ejs-identity/api/auth/logout').get({
|
||||
appKey : "system"
|
||||
});
|
||||
|
||||
localStorage.removeItem("token");
|
||||
$http.defaults.headers.common['token'] = null;
|
||||
|
||||
auth.loggedIn = false;
|
||||
auth.root = false;
|
||||
|
||||
$location.url("/");
|
||||
};
|
||||
|
||||
return auth;
|
||||
});
|
1835
ui/src/main/resources/META-INF/resources/lib/angular/angular-bootstrap-prettify.js
vendored
Normal file
175
ui/src/main/resources/META-INF/resources/lib/angular/angular-bootstrap.js
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
var directive = {};
|
||||
|
||||
directive.dropdownToggle =
|
||||
['$document', '$location', '$window',
|
||||
function ($document, $location, $window) {
|
||||
var openElement = null, close;
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.parent().bind('click', function(event) {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.bind('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var iWasOpen = false;
|
||||
|
||||
if (openElement) {
|
||||
iWasOpen = openElement === element;
|
||||
close();
|
||||
}
|
||||
|
||||
if (!iWasOpen){
|
||||
element.parent().addClass('open');
|
||||
openElement = element;
|
||||
|
||||
close = function (event) {
|
||||
event && event.preventDefault();
|
||||
event && event.stopPropagation();
|
||||
$document.unbind('click', close);
|
||||
element.parent().removeClass('open');
|
||||
close = null;
|
||||
openElement = null;
|
||||
}
|
||||
|
||||
$document.bind('click', close);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
directive.tabbable = function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
compile: function(element) {
|
||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
||||
tabContent = angular.element('<div class="tab-content"></div>');
|
||||
|
||||
tabContent.append(element.contents());
|
||||
element.append(navTabs).append(tabContent);
|
||||
},
|
||||
controller: ['$scope', '$element', function($scope, $element) {
|
||||
var navTabs = $element.contents().eq(0),
|
||||
ngModel = $element.controller('ngModel') || {},
|
||||
tabs = [],
|
||||
selectedTab;
|
||||
|
||||
ngModel.$render = function() {
|
||||
var $viewValue = this.$viewValue;
|
||||
|
||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
||||
if(selectedTab) {
|
||||
selectedTab.paneElement.removeClass('active');
|
||||
selectedTab.tabElement.removeClass('active');
|
||||
selectedTab = null;
|
||||
}
|
||||
if($viewValue) {
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
||||
if ($viewValue == tabs[i].value) {
|
||||
selectedTab = tabs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedTab) {
|
||||
selectedTab.paneElement.addClass('active');
|
||||
selectedTab.tabElement.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.addPane = function(element, attr) {
|
||||
var li = angular.element('<li><a href></a></li>'),
|
||||
a = li.find('a'),
|
||||
tab = {
|
||||
paneElement: element,
|
||||
paneAttrs: attr,
|
||||
tabElement: li
|
||||
};
|
||||
|
||||
tabs.push(tab);
|
||||
|
||||
attr.$observe('value', update)();
|
||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
||||
|
||||
function update() {
|
||||
tab.title = attr.title;
|
||||
tab.value = attr.value || attr.title;
|
||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
}
|
||||
ngModel.$render();
|
||||
}
|
||||
|
||||
navTabs.append(li);
|
||||
li.bind('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (ngModel.$setViewValue) {
|
||||
$scope.$apply(function() {
|
||||
ngModel.$setViewValue(tab.value);
|
||||
ngModel.$render();
|
||||
});
|
||||
} else {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
ngModel.$render();
|
||||
}
|
||||
});
|
||||
|
||||
return function() {
|
||||
tab.tabElement.remove();
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
||||
if (tab == tabs[i]) {
|
||||
tabs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
directive.table = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
element[0].className = 'table table-bordered table-striped code-table';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
directive.tabPane = function() {
|
||||
return {
|
||||
require: '^tabbable',
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs, tabsCtrl) {
|
||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
angular.module('bootstrap', []).directive(directive);
|
||||
|
||||
|
||||
})(window, window.angular);
|
185
ui/src/main/resources/META-INF/resources/lib/angular/angular-cookies.js
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngCookies
|
||||
*/
|
||||
|
||||
|
||||
angular.module('ngCookies', ['ng']).
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name ngCookies.$cookies
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Provides read/write access to browser's cookies.
|
||||
*
|
||||
* Only a simple Object is exposed and by adding or removing properties to/from
|
||||
* this object, new cookies are created/deleted at the end of current $eval.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function ExampleController($cookies) {
|
||||
// Retrieving a cookie
|
||||
var favoriteCookie = $cookies.myFavorite;
|
||||
// Setting a cookie
|
||||
$cookies.myFavorite = 'oatmeal';
|
||||
}
|
||||
</script>
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
*/
|
||||
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
|
||||
var cookies = {},
|
||||
lastCookies = {},
|
||||
lastBrowserCookies,
|
||||
runEval = false,
|
||||
copy = angular.copy,
|
||||
isUndefined = angular.isUndefined;
|
||||
|
||||
//creates a poller fn that copies all cookies from the $browser to service & inits the service
|
||||
$browser.addPollFn(function() {
|
||||
var currentCookies = $browser.cookies();
|
||||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
|
||||
lastBrowserCookies = currentCookies;
|
||||
copy(currentCookies, lastCookies);
|
||||
copy(currentCookies, cookies);
|
||||
if (runEval) $rootScope.$apply();
|
||||
}
|
||||
})();
|
||||
|
||||
runEval = true;
|
||||
|
||||
//at the end of each eval, push cookies
|
||||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
|
||||
// strings or browser refuses to store some cookies, we update the model in the push fn.
|
||||
$rootScope.$watch(push);
|
||||
|
||||
return cookies;
|
||||
|
||||
|
||||
/**
|
||||
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
|
||||
*/
|
||||
function push() {
|
||||
var name,
|
||||
value,
|
||||
browserCookies,
|
||||
updated;
|
||||
|
||||
//delete any cookies deleted in $cookies
|
||||
for (name in lastCookies) {
|
||||
if (isUndefined(cookies[name])) {
|
||||
$browser.cookies(name, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
//update all cookies updated in $cookies
|
||||
for(name in cookies) {
|
||||
value = cookies[name];
|
||||
if (!angular.isString(value)) {
|
||||
if (angular.isDefined(lastCookies[name])) {
|
||||
cookies[name] = lastCookies[name];
|
||||
} else {
|
||||
delete cookies[name];
|
||||
}
|
||||
} else if (value !== lastCookies[name]) {
|
||||
$browser.cookies(name, value);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
//verify what was actually stored
|
||||
if (updated){
|
||||
updated = false;
|
||||
browserCookies = $browser.cookies();
|
||||
|
||||
for (name in cookies) {
|
||||
if (cookies[name] !== browserCookies[name]) {
|
||||
//delete or reset all cookies that the browser dropped from $cookies
|
||||
if (isUndefined(browserCookies[name])) {
|
||||
delete cookies[name];
|
||||
} else {
|
||||
cookies[name] = browserCookies[name];
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]).
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name ngCookies.$cookieStore
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by angular's toJson/fromJson.
|
||||
* @example
|
||||
*/
|
||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
||||
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngCookies.$cookieStore#get
|
||||
* @methodOf ngCookies.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
*/
|
||||
get: function(key) {
|
||||
var value = $cookies[key];
|
||||
return value ? angular.fromJson(value) : value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngCookies.$cookieStore#put
|
||||
* @methodOf ngCookies.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$cookies[key] = angular.toJson(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngCookies.$cookieStore#remove
|
||||
* @methodOf ngCookies.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
*/
|
||||
remove: function(key) {
|
||||
delete $cookies[key];
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
|
||||
})(window, window.angular);
|
277
ui/src/main/resources/META-INF/resources/lib/angular/angular-loader.js
vendored
Normal file
|
@ -0,0 +1,277 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
(
|
||||
|
||||
/**
|
||||
* @ngdoc interface
|
||||
* @name angular.Module
|
||||
* @description
|
||||
*
|
||||
* Interface for configuring angular {@link angular.module modules}.
|
||||
*/
|
||||
|
||||
function setupModuleLoader(window) {
|
||||
|
||||
function ensure(obj, name, factory) {
|
||||
return obj[name] || (obj[name] = factory());
|
||||
}
|
||||
|
||||
return ensure(ensure(window, 'angular', Object), 'module', function() {
|
||||
/** @type {Object.<string, angular.Module>} */
|
||||
var modules = {};
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module
|
||||
* @description
|
||||
*
|
||||
* The `angular.module` is a global place for creating and registering Angular modules. All
|
||||
* modules (angular core or 3rd party) that should be available to an application must be
|
||||
* registered using this mechanism.
|
||||
*
|
||||
*
|
||||
* # Module
|
||||
*
|
||||
* A module is a collocation of services, directives, filters, and configuration information. Module
|
||||
* is used to configure the {@link AUTO.$injector $injector}.
|
||||
*
|
||||
* <pre>
|
||||
* // Create a new module
|
||||
* var myModule = angular.module('myModule', []);
|
||||
*
|
||||
* // register a new service
|
||||
* myModule.value('appName', 'MyCoolApp');
|
||||
*
|
||||
* // configure existing services inside initialization blocks.
|
||||
* myModule.config(function($locationProvider) {
|
||||
'use strict';
|
||||
* // Configure existing providers
|
||||
* $locationProvider.hashPrefix('!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* Then you can create an injector and load your modules like this:
|
||||
*
|
||||
* <pre>
|
||||
* var injector = angular.injector(['ng', 'MyModule'])
|
||||
* </pre>
|
||||
*
|
||||
* However it's more likely that you'll just use
|
||||
* {@link ng.directive:ngApp ngApp} or
|
||||
* {@link angular.bootstrap} to simplify this process for you.
|
||||
*
|
||||
* @param {!string} name The name of the module to create or retrieve.
|
||||
* @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
|
||||
* the module is being retrieved for further configuration.
|
||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
||||
* {@link angular.Module#config Module#config()}.
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
return function module(name, requires, configFn) {
|
||||
if (requires && modules.hasOwnProperty(name)) {
|
||||
modules[name] = null;
|
||||
}
|
||||
return ensure(modules, name, function() {
|
||||
if (!requires) {
|
||||
throw Error('No module: ' + name);
|
||||
}
|
||||
|
||||
/** @type {!Array.<Array.<*>>} */
|
||||
var invokeQueue = [];
|
||||
|
||||
/** @type {!Array.<Function>} */
|
||||
var runBlocks = [];
|
||||
|
||||
var config = invokeLater('$injector', 'invoke');
|
||||
|
||||
/** @type {angular.Module} */
|
||||
var moduleInstance = {
|
||||
// Private state
|
||||
_invokeQueue: invokeQueue,
|
||||
_runBlocks: runBlocks,
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name angular.Module#requires
|
||||
* @propertyOf angular.Module
|
||||
* @returns {Array.<string>} List of module names which must be loaded before this module.
|
||||
* @description
|
||||
* Holds the list of modules which the injector will load before the current module is loaded.
|
||||
*/
|
||||
requires: requires,
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name angular.Module#name
|
||||
* @propertyOf angular.Module
|
||||
* @returns {string} Name of the module.
|
||||
* @description
|
||||
*/
|
||||
name: name,
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#provider
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name service name
|
||||
* @param {Function} providerType Construction function for creating new instance of the service.
|
||||
* @description
|
||||
* See {@link AUTO.$provide#provider $provide.provider()}.
|
||||
*/
|
||||
provider: invokeLater('$provide', 'provider'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#factory
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name service name
|
||||
* @param {Function} providerFunction Function for creating new instance of the service.
|
||||
* @description
|
||||
* See {@link AUTO.$provide#factory $provide.factory()}.
|
||||
*/
|
||||
factory: invokeLater('$provide', 'factory'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#service
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name service name
|
||||
* @param {Function} constructor A constructor function that will be instantiated.
|
||||
* @description
|
||||
* See {@link AUTO.$provide#service $provide.service()}.
|
||||
*/
|
||||
service: invokeLater('$provide', 'service'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#value
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name service name
|
||||
* @param {*} object Service instance object.
|
||||
* @description
|
||||
* See {@link AUTO.$provide#value $provide.value()}.
|
||||
*/
|
||||
value: invokeLater('$provide', 'value'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#constant
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name constant name
|
||||
* @param {*} object Constant value.
|
||||
* @description
|
||||
* Because the constant are fixed, they get applied before other provide methods.
|
||||
* See {@link AUTO.$provide#constant $provide.constant()}.
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#filter
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name Filter name.
|
||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
||||
* @description
|
||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
||||
*/
|
||||
filter: invokeLater('$filterProvider', 'register'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#controller
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name Controller name.
|
||||
* @param {Function} constructor Controller constructor function.
|
||||
* @description
|
||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
||||
*/
|
||||
controller: invokeLater('$controllerProvider', 'register'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#directive
|
||||
* @methodOf angular.Module
|
||||
* @param {string} name directive name
|
||||
* @param {Function} directiveFactory Factory function for creating new instance of
|
||||
* directives.
|
||||
* @description
|
||||
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
|
||||
*/
|
||||
directive: invokeLater('$compileProvider', 'directive'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#config
|
||||
* @methodOf angular.Module
|
||||
* @param {Function} configFn Execute this function on module load. Useful for service
|
||||
* configuration.
|
||||
* @description
|
||||
* Use this method to register work which needs to be performed on module loading.
|
||||
*/
|
||||
config: config,
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#run
|
||||
* @methodOf angular.Module
|
||||
* @param {Function} initializationFn Execute this function after injector creation.
|
||||
* Useful for application initialization.
|
||||
* @description
|
||||
* Use this method to register work which should be performed when the injector is done
|
||||
* loading all modules.
|
||||
*/
|
||||
run: function(block) {
|
||||
runBlocks.push(block);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
if (configFn) {
|
||||
config(configFn);
|
||||
}
|
||||
|
||||
return moduleInstance;
|
||||
|
||||
/**
|
||||
* @param {string} provider
|
||||
* @param {string} method
|
||||
* @param {String=} insertMethod
|
||||
* @returns {angular.Module}
|
||||
*/
|
||||
function invokeLater(provider, method, insertMethod) {
|
||||
return function() {
|
||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
|
||||
return moduleInstance;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
)(window);
|
||||
|
||||
/**
|
||||
* Closure compiler type information
|
||||
*
|
||||
* @typedef { {
|
||||
* requires: !Array.<string>,
|
||||
* invokeQueue: !Array.<Array.<*>>,
|
||||
*
|
||||
* service: function(string, Function):angular.Module,
|
||||
* factory: function(string, Function):angular.Module,
|
||||
* value: function(string, *):angular.Module,
|
||||
*
|
||||
* filter: function(string, Function):angular.Module,
|
||||
*
|
||||
* init: function(Function):angular.Module
|
||||
* } }
|
||||
*/
|
||||
angular.Module;
|
||||
|
1788
ui/src/main/resources/META-INF/resources/lib/angular/angular-mocks.js
vendored
Normal file
457
ui/src/main/resources/META-INF/resources/lib/angular/angular-resource.js
vendored
Normal file
|
@ -0,0 +1,457 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngResource
|
||||
* @description
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name ngResource.$resource
|
||||
* @requires $http
|
||||
*
|
||||
* @description
|
||||
* A factory which creates a resource object that lets you interact with
|
||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
||||
*
|
||||
* The returned resource object has action methods which provide high-level behaviors without
|
||||
* the need to interact with the low level {@link ng.$http $http} service.
|
||||
*
|
||||
* # Installation
|
||||
* To use $resource make sure you have included the `angular-resource.js` that comes in Angular
|
||||
* package. You can also find this file on Google CDN, bower as well as at
|
||||
* {@link http://code.angularjs.org/ code.angularjs.org}.
|
||||
*
|
||||
* Finally load the module in your application:
|
||||
*
|
||||
* angular.module('app', ['ngResource']);
|
||||
*
|
||||
* and you are ready to get started!
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
|
||||
* number, like this: `$resource('http://example.com\\:8080/api')`.
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods.
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
*
|
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
||||
* URL `/path/greet?salutation=Hello`.
|
||||
*
|
||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
||||
* the data object (useful for non-GET operations).
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
|
||||
* default set of resource actions. The declaration should be created in the following format:
|
||||
*
|
||||
* {action1: {method:?, params:?, isArray:?},
|
||||
* action2: {method:?, params:?, isArray:?},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `action` – {string} – The name of action. This name becomes the name of the method on your
|
||||
* resource object.
|
||||
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and `JSONP`
|
||||
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
|
||||
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
|
||||
* `returns` section.
|
||||
*
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
*
|
||||
* { 'get': {method:'GET'},
|
||||
* 'save': {method:'POST'},
|
||||
* 'query': {method:'GET', isArray:true},
|
||||
* 'remove': {method:'DELETE'},
|
||||
* 'delete': {method:'DELETE'} };
|
||||
*
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* <pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It is important to realize that invoking a $resource object method immediately returns an
|
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
* object results in no rendering, once the data arrives from the server then the object is
|
||||
* populated with the data and the view automatically re-renders itself showing the new data. This
|
||||
* means that in most case one never has to write a callback function for the action methods.
|
||||
*
|
||||
* The action methods on the class object or instance object can be invoked with the following
|
||||
* parameters:
|
||||
*
|
||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
*
|
||||
* <pre>
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query(function() {
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0];
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
});
|
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* </pre>
|
||||
*
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(u, getResponseHeaders){
|
||||
u.abc = true;
|
||||
u.$save(function(u, putResponseHeaders) {
|
||||
//u => saved user object
|
||||
//putResponseHeaders => $http header getter
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
* # Buzz client
|
||||
|
||||
Let's look at what a buzz client created with the `$resource` service looks like:
|
||||
<doc:example>
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
function BuzzController($resource) {
|
||||
this.userId = 'googlebuzz';
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
|
||||
);
|
||||
}
|
||||
|
||||
BuzzController.prototype = {
|
||||
fetch: function() {
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
BuzzController.$inject = ['$resource'];
|
||||
</script>
|
||||
|
||||
<div ng-controller="BuzzController">
|
||||
<input ng-model="userId"/>
|
||||
<button ng-click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div ng-repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
factory('$resource', ['$http', '$parse', function($http, $parse) {
|
||||
var DEFAULT_ACTIONS = {
|
||||
'get': {method:'GET'},
|
||||
'save': {method:'POST'},
|
||||
'query': {method:'GET', isArray:true},
|
||||
'remove': {method:'DELETE'},
|
||||
'delete': {method:'DELETE'}
|
||||
};
|
||||
var noop = angular.noop,
|
||||
forEach = angular.forEach,
|
||||
extend = angular.extend,
|
||||
copy = angular.copy,
|
||||
isFunction = angular.isFunction,
|
||||
getter = function(obj, path) {
|
||||
return $parse(path)(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
||||
* segments:
|
||||
* segment = *pchar
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriSegment(val) {
|
||||
return encodeUriQuery(val, true).
|
||||
replace(/%26/gi, '&').
|
||||
replace(/%3D/gi, '=').
|
||||
replace(/%2B/gi, '+');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
|
||||
* method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
|
||||
* encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
return encodeURIComponent(val).
|
||||
replace(/%40/gi, '@').
|
||||
replace(/%3A/gi, ':').
|
||||
replace(/%24/g, '$').
|
||||
replace(/%2C/gi, ',').
|
||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
|
||||
function Route(template, defaults) {
|
||||
this.template = template = template + '#';
|
||||
this.defaults = defaults || {};
|
||||
var urlParams = this.urlParams = {};
|
||||
forEach(template.split(/\W/), function(param){
|
||||
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
});
|
||||
this.template = template.replace(/\\:/g, ':');
|
||||
}
|
||||
|
||||
Route.prototype = {
|
||||
url: function(params) {
|
||||
var self = this,
|
||||
url = this.template,
|
||||
val,
|
||||
encodedVal;
|
||||
|
||||
params = params || {};
|
||||
forEach(this.urlParams, function(_, urlParam){
|
||||
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
|
||||
if (angular.isDefined(val) && val !== null) {
|
||||
encodedVal = encodeUriSegment(val);
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1");
|
||||
} else {
|
||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match,
|
||||
leadingSlashes, tail) {
|
||||
if (tail.charAt(0) == '/') {
|
||||
return tail;
|
||||
} else {
|
||||
return leadingSlashes + tail;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
forEach(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
|
||||
}
|
||||
});
|
||||
query.sort();
|
||||
url = url.replace(/\/*$/, '');
|
||||
return url + (query.length ? '?' + query.join('&') : '');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function ResourceFactory(url, paramDefaults, actions) {
|
||||
var route = new Route(url);
|
||||
|
||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
||||
|
||||
function extractParams(data, actionParams){
|
||||
var ids = {};
|
||||
actionParams = extend({}, paramDefaults, actionParams);
|
||||
forEach(actionParams, function(value, key){
|
||||
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function Resource(value){
|
||||
copy(value || {}, this);
|
||||
}
|
||||
|
||||
forEach(actions, function(action, name) {
|
||||
action.method = angular.uppercase(action.method);
|
||||
var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
|
||||
Resource[name] = function(a1, a2, a3, a4) {
|
||||
var params = {};
|
||||
var data;
|
||||
var success = noop;
|
||||
var error = null;
|
||||
switch(arguments.length) {
|
||||
case 4:
|
||||
error = a4;
|
||||
success = a3;
|
||||
//fallthrough
|
||||
case 3:
|
||||
case 2:
|
||||
if (isFunction(a2)) {
|
||||
if (isFunction(a1)) {
|
||||
success = a1;
|
||||
error = a2;
|
||||
break;
|
||||
}
|
||||
|
||||
success = a2;
|
||||
error = a3;
|
||||
//fallthrough
|
||||
} else {
|
||||
params = a1;
|
||||
data = a2;
|
||||
success = a3;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
if (isFunction(a1)) success = a1;
|
||||
else if (hasBody) data = a1;
|
||||
else params = a1;
|
||||
break;
|
||||
case 0: break;
|
||||
default:
|
||||
throw "Expected between 0-4 arguments [params, data, success, error], got " +
|
||||
arguments.length + " arguments.";
|
||||
}
|
||||
|
||||
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
|
||||
$http({
|
||||
method: action.method,
|
||||
url: route.url(extend({}, extractParams(data, action.params || {}), params)),
|
||||
data: data
|
||||
}).then(function(response) {
|
||||
var data = response.data;
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
}
|
||||
}
|
||||
(success||noop)(value, response.headers);
|
||||
}, error);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
Resource.prototype['$' + name] = function(a1, a2, a3) {
|
||||
var params = extractParams(this),
|
||||
success = noop,
|
||||
error;
|
||||
|
||||
switch(arguments.length) {
|
||||
case 3: params = a1; success = a2; error = a3; break;
|
||||
case 2:
|
||||
case 1:
|
||||
if (isFunction(a1)) {
|
||||
success = a1;
|
||||
error = a2;
|
||||
} else {
|
||||
params = a1;
|
||||
success = a2 || noop;
|
||||
}
|
||||
case 0: break;
|
||||
default:
|
||||
throw "Expected between 1-3 arguments [params, success, error], got " +
|
||||
arguments.length + " arguments.";
|
||||
}
|
||||
var data = hasBody ? this : undefined;
|
||||
Resource[name].call(this, params, data, success, error);
|
||||
};
|
||||
});
|
||||
|
||||
Resource.bind = function(additionalParamDefaults){
|
||||
return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
||||
};
|
||||
|
||||
return Resource;
|
||||
}
|
||||
|
||||
return ResourceFactory;
|
||||
}]);
|
||||
|
||||
|
||||
})(window, window.angular);
|
537
ui/src/main/resources/META-INF/resources/lib/angular/angular-sanitize.js
vendored
Normal file
|
@ -0,0 +1,537 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngSanitize
|
||||
* @description
|
||||
*/
|
||||
|
||||
/*
|
||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
||||
* based on: HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name ngSanitize.$sanitize
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
||||
* it into the returned string, however, since our parser is more strict than a typical browser
|
||||
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
||||
* browser, won't make it through the sanitizer.
|
||||
*
|
||||
* @param {string} html Html input.
|
||||
* @returns {string} Sanitized html.
|
||||
*
|
||||
* @example
|
||||
<doc:example module="ngSanitize">
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.snippet =
|
||||
'<p style="color:blue">an html\n' +
|
||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
||||
'snippet</p>';
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="html-filter">
|
||||
<td>html filter</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippet"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng-bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng-bind-html-unsafe="snippet"><br/></div></pre></td>
|
||||
<td><div ng-bind-html-unsafe="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should sanitize the html snippet ', function() {
|
||||
expect(using('#html-filter').element('div').html()).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
|
||||
it('should escape snippet without any filter', function() {
|
||||
expect(using('#escaped-html').element('div').html()).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should inline raw snippet if filtered as unsafe', function() {
|
||||
expect(using('#html-unsafe-filter').element("div").html()).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('snippet').enter('new <b>text</b>');
|
||||
expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
|
||||
expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
var $sanitize = function(html) {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
|
||||
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
||||
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
|
||||
|
||||
|
||||
// Good source of info about elements and attributes
|
||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
||||
// http://simon.html5.org/html-elements
|
||||
|
||||
// Safe Void Elements - HTML5
|
||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
||||
|
||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
||||
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
||||
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
|
||||
optionalEndTagInlineElements = makeMap("rp,rt"),
|
||||
optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
|
||||
|
||||
// Safe Block Elements - HTML5
|
||||
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
|
||||
"blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
|
||||
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
||||
|
||||
// Inline Elements - HTML5
|
||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
|
||||
"big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
|
||||
"span,strike,strong,sub,sup,time,tt,u,var"));
|
||||
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
||||
var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
|
||||
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
|
||||
var validAttrs = angular.extend({}, uriAttrs, makeMap(
|
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
||||
'valign,value,vspace,width'));
|
||||
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(','), i;
|
||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @example
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* @param {string} html string
|
||||
* @param {object} handler
|
||||
*/
|
||||
function htmlParser( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") === 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if (handler.comment) handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
||||
match = html.match( END_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
||||
match = html.match( START_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
|
||||
text = text.
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last ) {
|
||||
throw "Parse Error: " + html;
|
||||
}
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
tagName = angular.lowercase(tagName);
|
||||
if ( blockElements[ tagName ] ) {
|
||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = voidElements[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
|
||||
var value = doubleQuotedValue
|
||||
|| singleQoutedValue
|
||||
|| unqoutedValue
|
||||
|| '';
|
||||
|
||||
attrs[name] = decodeEntities(value);
|
||||
});
|
||||
if (handler.start) handler.start( tagName, attrs, unary );
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
var pos = 0, i;
|
||||
tagName = angular.lowercase(tagName);
|
||||
if ( tagName )
|
||||
// Find the closest opened tag of the same type
|
||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
if (handler.end) handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* decodes all entities into regular string
|
||||
* @param value
|
||||
* @returns {string} A string with decoded entities.
|
||||
*/
|
||||
var hiddenPre=document.createElement("pre");
|
||||
function decodeEntities(value) {
|
||||
hiddenPre.innerHTML=value.replace(/</g,"<");
|
||||
return hiddenPre.innerText || hiddenPre.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all potentially dangerous characters, so that the
|
||||
* resulting string can be safely inserted into attribute or
|
||||
* element text.
|
||||
* @param value
|
||||
* @returns escaped text
|
||||
*/
|
||||
function encodeEntities(value) {
|
||||
return value.
|
||||
replace(/&/g, '&').
|
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
||||
return '&#' + value.charCodeAt(0) + ';';
|
||||
}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* create an HTML/XML writer which writes to buffer
|
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
||||
* @returns {object} in the form of {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* }
|
||||
*/
|
||||
function htmlSanitizeWriter(buf){
|
||||
var ignore = false;
|
||||
var out = angular.bind(buf, buf.push);
|
||||
return {
|
||||
start: function(tag, attrs, unary){
|
||||
tag = angular.lowercase(tag);
|
||||
if (!ignore && specialElements[tag]) {
|
||||
ignore = tag;
|
||||
}
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('<');
|
||||
out(tag);
|
||||
angular.forEach(attrs, function(value, key){
|
||||
var lkey=angular.lowercase(key);
|
||||
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
|
||||
out(' ');
|
||||
out(key);
|
||||
out('="');
|
||||
out(encodeEntities(value));
|
||||
out('"');
|
||||
}
|
||||
});
|
||||
out(unary ? '/>' : '>');
|
||||
}
|
||||
},
|
||||
end: function(tag){
|
||||
tag = angular.lowercase(tag);
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('</');
|
||||
out(tag);
|
||||
out('>');
|
||||
}
|
||||
if (tag == ignore) {
|
||||
ignore = false;
|
||||
}
|
||||
},
|
||||
chars: function(chars){
|
||||
if (!ignore) {
|
||||
out(encodeEntities(chars));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// define ngSanitize module and register $sanitize service
|
||||
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngSanitize.directive:ngBindHtml
|
||||
*
|
||||
* @description
|
||||
* Creates a binding that will sanitize the result of evaluating the `expression` with the
|
||||
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
|
||||
*
|
||||
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
|
||||
*/
|
||||
angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
|
||||
return function(scope, element, attr) {
|
||||
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
|
||||
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
|
||||
value = $sanitize(value);
|
||||
element.html(value || '');
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name ngSanitize.filter:linky
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
||||
* plain email address links.
|
||||
*
|
||||
* @param {string} text Input text.
|
||||
* @returns {string} Html-linkified text.
|
||||
*
|
||||
* @usage
|
||||
<span ng-bind-html="linky_expression | linky"></span>
|
||||
*
|
||||
* @example
|
||||
<doc:example module="ngSanitize">
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.snippet =
|
||||
'Pretty text with some links:\n'+
|
||||
'http://angularjs.org/,\n'+
|
||||
'mailto:us@somewhere.org,\n'+
|
||||
'another@somewhere.org,\n'+
|
||||
'and one more: ftp://127.0.0.1/.';
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="linky-filter">
|
||||
<td>linky filter</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
||||
<td><div ng-bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should linkify the snippet with urls', function() {
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links: ' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>, ' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>, ' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' +
|
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
||||
});
|
||||
|
||||
it ('should not linkify snippet without the linky filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("Pretty text with some links:\n" +
|
||||
"http://angularjs.org/,\n" +
|
||||
"mailto:us@somewhere.org,\n" +
|
||||
"another@somewhere.org,\n" +
|
||||
"and one more: ftp://127.0.0.1/.");
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('snippet').enter('new http://link.');
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angular.module('ngSanitize').filter('linky', function() {
|
||||
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
return function(text) {
|
||||
if (!text) return text;
|
||||
var match;
|
||||
var raw = text;
|
||||
var html = [];
|
||||
// TODO(vojta): use $sanitize instead
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
i = match.index;
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + match[0].length);
|
||||
}
|
||||
writer.chars(raw);
|
||||
return html.join('');
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
})(window, window.angular);
|
26310
ui/src/main/resources/META-INF/resources/lib/angular/angular-scenario.js
vendored
Normal file
14847
ui/src/main/resources/META-INF/resources/lib/angular/angular.js
vendored
Normal file
6
ui/src/main/resources/META-INF/resources/lib/angular/jstd-scenario-adapter-config.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Configuration for jstd scenario adapter
|
||||
*/
|
||||
var jstdScenarioAdapter = {
|
||||
relativeUrlPrefix: '/build/docs/'
|
||||
};
|
185
ui/src/main/resources/META-INF/resources/lib/angular/jstd-scenario-adapter.js
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* @license AngularJS v1.0.5
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* JSTestDriver adapter for angular scenario tests
|
||||
*
|
||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
||||
<pre>
|
||||
server: http://localhost:9877
|
||||
|
||||
load:
|
||||
- lib/angular-scenario.js
|
||||
- lib/jstd-scenario-adapter-config.js
|
||||
- lib/jstd-scenario-adapter.js
|
||||
# your test files go here #
|
||||
|
||||
proxy:
|
||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
||||
</pre>
|
||||
*
|
||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
||||
* Note the order of files - it's important !
|
||||
*
|
||||
* Example of jstd-scenario-adapter-config.js
|
||||
<pre>
|
||||
var jstdScenarioAdapter = {
|
||||
relativeUrlPrefix: '/your-prefix/'
|
||||
};
|
||||
</pre>
|
||||
*
|
||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
||||
* You have to configure this to work together with JSTD proxy.
|
||||
*
|
||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom type of test case
|
||||
*
|
||||
* @const
|
||||
* @see jstestdriver.TestCaseInfo
|
||||
*/
|
||||
var SCENARIO_TYPE = 'scenario';
|
||||
|
||||
/**
|
||||
* Plugin for JSTestDriver
|
||||
* Connection point between scenario's jstd output and jstestdriver.
|
||||
*
|
||||
* @see jstestdriver.PluginRegistrar
|
||||
*/
|
||||
function JstdPlugin() {
|
||||
var nop = function() {};
|
||||
|
||||
this.reportResult = nop;
|
||||
this.reportEnd = nop;
|
||||
this.runScenario = nop;
|
||||
|
||||
this.name = 'Angular Scenario Adapter';
|
||||
|
||||
/**
|
||||
* Called for each JSTD TestCase
|
||||
*
|
||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
||||
*
|
||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
||||
* @param {Function} onTestDone
|
||||
* @param {Function} onAllTestsComplete
|
||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
||||
*/
|
||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
||||
|
||||
this.reportResult = onTestDone;
|
||||
this.reportEnd = onAllTestsComplete;
|
||||
this.runScenario();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
||||
testRunsConfiguration.push(
|
||||
new jstestdriver.TestRunConfiguration(
|
||||
new jstestdriver.TestCaseInfo(
|
||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance of the plugin
|
||||
* Accessed using closure by:
|
||||
* - jstd output (reports to this plugin)
|
||||
* - initScenarioAdapter (register the plugin to jstd)
|
||||
*/
|
||||
var plugin = new JstdPlugin();
|
||||
|
||||
/**
|
||||
* Initialise scenario jstd-adapter
|
||||
* (only if jstestdriver is defined)
|
||||
*
|
||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
||||
* @param {Object=} config Configuration object, supported properties:
|
||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
||||
*/
|
||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
||||
if (jstestdriver) {
|
||||
// create and register ScenarioPlugin
|
||||
jstestdriver.pluginRegistrar.register(plugin);
|
||||
plugin.runScenario = initScenarioAndRun;
|
||||
|
||||
/**
|
||||
* HACK (angular.scenario.Application.navigateTo)
|
||||
*
|
||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
||||
* because we want to allow running scenario tests without creating its own virtual host.
|
||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
||||
*
|
||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
||||
*
|
||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
||||
*/
|
||||
var appProto = angular.scenario.Application.prototype,
|
||||
navigateTo = appProto.navigateTo,
|
||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
||||
|
||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
||||
url != 'about:blank' && !url.match(/^https?/)) {
|
||||
url = relativeUrlPrefix + url;
|
||||
}
|
||||
|
||||
return navigateTo.call(this, url, loadFn, errorFn);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds proper TestResult object from given model spec
|
||||
*
|
||||
* TODO(vojta) report error details
|
||||
*
|
||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
||||
* @returns {jstestdriver.TestResult}
|
||||
*/
|
||||
function createTestResultFromSpec(spec) {
|
||||
var map = {
|
||||
success: 'PASSED',
|
||||
error: 'ERROR',
|
||||
failure: 'FAILED'
|
||||
};
|
||||
|
||||
return new jstestdriver.TestResult(
|
||||
spec.fullDefinitionName,
|
||||
spec.name,
|
||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
||||
spec.error || '',
|
||||
spec.line || '',
|
||||
spec.duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSTD output (jstestdriver.TestResult)
|
||||
*/
|
||||
angular.scenario.output('jstd', function(context, runner, model) {
|
||||
model.on('SpecEnd', function(spec) {
|
||||
plugin.reportResult(createTestResultFromSpec(spec));
|
||||
});
|
||||
|
||||
model.on('RunnerEnd', function() {
|
||||
plugin.reportEnd();
|
||||
});
|
||||
});
|
||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
||||
})(window);
|
|
@ -0,0 +1 @@
|
|||
{"full":"1.0.7","major":"1","minor":"0","dot":"7","codename":"monochromatic-rainbow","cdn":"1.0.6"}
|
|
@ -0,0 +1 @@
|
|||
1.0.7
|
1109
ui/src/main/resources/META-INF/resources/lib/bootstrap/css/bootstrap-responsive.css
vendored
Normal file
9
ui/src/main/resources/META-INF/resources/lib/bootstrap/css/bootstrap-responsive.min.css
vendored
Normal file
6167
ui/src/main/resources/META-INF/resources/lib/bootstrap/css/bootstrap.css
vendored
Normal file
9
ui/src/main/resources/META-INF/resources/lib/bootstrap/css/bootstrap.min.css
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 12 KiB |
2280
ui/src/main/resources/META-INF/resources/lib/bootstrap/js/bootstrap.js
vendored
Normal file
6
ui/src/main/resources/META-INF/resources/lib/bootstrap/js/bootstrap.min.js
vendored
Normal file
9789
ui/src/main/resources/META-INF/resources/lib/jquery/jquery-1.10.2.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/activities-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray">Events</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Context Path</th>
|
||||
<th>Page</th>
|
||||
<th>Remote Address</th>
|
||||
<th>Country</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="event in events">
|
||||
<td>{{event.time | date:'medium'}}</td>
|
||||
<td>{{event.contextPath}}</td>
|
||||
<td>{{event.page}}</td>
|
||||
<td>{{event.remoteAddr}}</td>
|
||||
<td>{{event.country}}</td>
|
||||
<td>{{event.userAgent}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
<nav id="local-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li>
|
||||
<div>
|
||||
<span class="toggle">Activities</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-class="!path[1]&& 'active'"><a href="#/activities">Statistics</a></li>
|
||||
<li ng-class="path[1] == 'pages' && 'active'"><a href="#/activities/pages">Pages</a></li>
|
||||
<li ng-class="path[1] == 'events' && 'active'"><a href="#/activities/events">Events</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -0,0 +1,26 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/activities-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray">Pages</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="event in statistics['pageViews']">
|
||||
<td>{{event.label}}</td>
|
||||
<td>{{event.count}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,30 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/activities-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray">Statistics</span>
|
||||
</h1>
|
||||
|
||||
<fieldset ng-repeat="i in ['Browser', 'Country', 'Language', 'OS', 'User']">
|
||||
<legend>{{i}}</legend>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="event in statistics[i.toLowerCase() + 'Views']">
|
||||
<td>{{event.label}}</td>
|
||||
<td>{{event.count}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,128 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/application-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray" ng-show="create">New Application</span> <span class="gray" ng-hide="create">{{application.name}}</span>
|
||||
configuration
|
||||
</h1>
|
||||
|
||||
<div ng-show="applicationForm.showErrors && applicationForm.$error.required" class="alert alert-error">Please fill in all required fields</div>
|
||||
<p class="subtitle subtitle-right"><span class="required">*</span> Required fields</p>
|
||||
|
||||
<form class="form-horizontal" name="applicationForm" novalidate>
|
||||
<fieldset>
|
||||
<legend>Settings</legend>
|
||||
<div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="name">Name <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="name" name="name" ng-model="application.name" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="callbackUrl">Callback URL <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xxlarge" id="callbackUrl" name="callbackUrl" ng-model="application.callbackUrl"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="javaScriptOrigin">JavaScript Origin </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xxlarge" id="javaScriptOrigin" ng-model="application.javaScriptOrigin">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="key">Key </label>
|
||||
<div class="controls">
|
||||
<input class="input-xxlarge" type="text" id="key" ng-model="application.key" ng-readonly="!(auth.root && create)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="secret">Secret </label>
|
||||
<div class="controls">
|
||||
<input class="input-xxlarge" type="text" id="secret" ng-model="application.secret" ng-readonly="!(auth.root && create)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="realm">Realm <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<select ng-model="application.realm" id="realm" name="realm" required>
|
||||
<option ng-repeat="r in realms" value="{{r.key}}" ng-selected="r.key == application.realm">{{r.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-show="auth.root">
|
||||
<label class="control-label" for="owner">Owner </label>
|
||||
<div class="controls">
|
||||
<input class="input-xxlarge" type="text" id="owner" ng-model="application.owner">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Identity Providers</legend>
|
||||
<div>
|
||||
<div class="input-append">
|
||||
<select ng-model="newProviderId">
|
||||
<option ng-repeat="p in availableProviders" value="{{p.id}}">{{p.name}}</option>
|
||||
</select>
|
||||
<button class="btn" ng-click="addProvider()" ng-disabled="!newProviderId">Add Provider</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered margin-top" ng-show="application.providers.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Provider</th>
|
||||
<th>Key <span class="required">*</span></th>
|
||||
<th>Secret <span class="required">*</span></th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="provider in application.providers">
|
||||
<td><input type="text" placeholder="Key" value="{{getProviderDescription(provider.providerId).name}}" readonly></td>
|
||||
<td>
|
||||
<input type="text" placeholder="Key" ng-model="provider.key" required>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" placeholder="Secret" ng-model="provider.secret" required>
|
||||
</td>
|
||||
<td><i class="icon-question-sign" ng-click="openHelp($index)"></i></td>
|
||||
<td><i class="icon-trash" ng-click="removeProvider($index)"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions" ng-show="create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save</button>
|
||||
<button type="submit" ng-click="cancel()" class="btn" ng-click="cancel()" ng-show="changed">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions" ng-show="!create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save changes</button>
|
||||
<button type="submit" ng-click="reset()" class="btn" ng-show="changed">Clear changes</button>
|
||||
<a href="#/applications" ng-hide="changed">View applications »</a>
|
||||
<button type="submit" ng-click="remove()" class="btn btn-danger" ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div modal="providerHelpModal" close="closeHelp()" options="opts">
|
||||
<div class="modal-header">
|
||||
<h3>Configure {{providerHelp.description.name}}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-include src="providerHelp && 'partials/provider/' + providerHelp.description.id + '-help.html'"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" ng-click="closeHelp()">Close</button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/application-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<a class="btn btn-small pull-right" href="#/applications/new">Add Application</a>
|
||||
|
||||
<h1>
|
||||
<span class="gray">Applications</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Application</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="application in applications">
|
||||
<td><a href="#/applications/{{application.key}}">{{application.name}}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<nav id="local-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li>
|
||||
<div>
|
||||
<span class="toggle">Applications</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-repeat="application in applications" ng-class="path[1] == application.key && 'active'"><a
|
||||
href="#/applications/{{application.key}}">{{application.name}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -0,0 +1,5 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
</div>
|
27
ui/src/main/resources/META-INF/resources/partials/menu.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<header class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<div class="nav-collapse">
|
||||
<nav id="global-nav">
|
||||
<ul class="nav">
|
||||
<li class="divider-vertical-left" ng-class="path[0] == '' && 'active'"><a href="#">Home</a></li>
|
||||
<li class="divider-vertical-left" ng-class="path[0] == 'applications' && 'active'" ng-show="auth.loggedIn"><a href="#/applications">Applications</a></li>
|
||||
<li class="divider-vertical-left" ng-class="path[0] == 'realms' && 'active'" ng-show="auth.loggedIn"><a href="#/realms">Realms</a></li>
|
||||
<li class="divider-vertical-left" ng-class="path[0] == 'activities' && 'active'" ng-show="auth.root"><a href="#/activities">Activities</a></li>
|
||||
</ul>
|
||||
<ul class="nav pull-right" ng-hide="auth.loggedIn">
|
||||
<li><a href="/ejs-identity/api/login/system">Login</a></li>
|
||||
<li><a href="/ejs-identity/api/register/system">Register</a></li>
|
||||
</ul>
|
||||
<ul class="nav pull-right" ng-show="auth.loggedIn">
|
||||
<li class="dropdown"><a data-toggle="dropdown" class="dropdown-toggle" href="#"><i
|
||||
class="icon-user icon-gray"></i> {{auth.user.displayName}} <i class="caret"></i></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="" ng-click="auth.logout()">Sign Out</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
|
@ -0,0 +1,2 @@
|
|||
<p>This provider is only intended for testing purposes and doesn't require any configuration. Simply use any value for key and
|
||||
secret.</p>
|
|
@ -0,0 +1,33 @@
|
|||
<p>
|
||||
Open <a href="https://code.google.com/apis/console/" target="_blank">https://code.google.com/apis/console/</a>. From the
|
||||
drop-down menu select <i>Create</i>.
|
||||
</p>
|
||||
|
||||
<p>Use any name that you'd like, click <i>Create Project</i>, select <i>API Access</i> and click on <i>Create an OAuth 2.0 client ID</i>.</p>
|
||||
|
||||
<p>Use any product name you'd like and leave the other fields empty, then click <i>Next</i>. On the next page select <i>Web application</i>
|
||||
as the application type. Click <i>more options next></i> to <i>Your site or hostname</i>. Fill in the form with the following values:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Authorized Redirect URIs:</b> {{callbackUrl}}</li>
|
||||
</ul>
|
||||
|
||||
<p>Click on <i>Create client ID</i>. Insert <i>Client ID</i> and <i>Client secret</i> in the form below.</p>
|
||||
|
||||
<form class="form-horizontal" name="googleHelpForm">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="providerHelp.key">Client ID </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="providerHelp.key" ng-model="application.providers[providerHelp.index].key">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="providerHelp.secret">Client secret </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="providerHelp.secret"
|
||||
ng-model="application.providers[providerHelp.index].secret">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p>Close the help panel and click <i>save changes</i>.</p>
|
|
@ -0,0 +1,41 @@
|
|||
<p>
|
||||
Open <a href="https://dev.twitter.com/apps" target="_blank">https://dev.twitter.com/apps</a>. Click on <i>Create a new
|
||||
application</i>.
|
||||
</p>
|
||||
|
||||
<p>Fill in name, description and website. For <i>Callback URL</i> use the following value:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Callback URL:</b> {{callbackUrl}}</li>
|
||||
</ul>
|
||||
|
||||
<p>Note: Twitter doesn't allow <b>localhost</b> as domain, use <b>127.0.0.1</b> instead!</p>
|
||||
|
||||
<p>Agree to the rules, fill in the captcha and click on <i>Create your Twitter application</i></p>
|
||||
|
||||
<p>Insert <i>Consumer key</i> and <i>Consumer secret</i> in the form below.</p>
|
||||
|
||||
<form class="form-horizontal" name="googleHelpForm">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="providerHelp.key">Consumer key </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="providerHelp.key" ng-model="application.providers[providerHelp.index].key">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="providerHelp.secret">Consumer secret </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="providerHelp.secret"
|
||||
ng-model="application.providers[providerHelp.index].secret">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
Now click on <i>Settings</i> and tick the box <i>Allow this application to be used to Sign in with Twitter</i>, and click on <i>Update
|
||||
this Twitter application's settings</i>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Close the help panel and click <i>save changes</i>.
|
||||
</p>
|
|
@ -0,0 +1,54 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/realm-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray" ng-show="create">New Realm</span>
|
||||
<span class="gray" ng-hide="create">{{realm.name}}</span> configuration
|
||||
</h1>
|
||||
|
||||
<div ng-show="realmForm.showErrors && realmForm.$error.required" class="alert alert-error">Please fill in all required fields</div>
|
||||
<p class="subtitle subtitle-right"><span class="required">*</span> Required fields</p>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend>Details</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="name">Name <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="name" name="name" ng-model="realm.name" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="key">Key </label>
|
||||
<div class="controls">
|
||||
<input class="input-xxlarge" type="text" id="key" name="key" ng-model="realm.key" ng-readonly="!(auth.root && create)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-show="auth.root">
|
||||
<label class="control-label" for="owner">Owner </label>
|
||||
<div class="controls">
|
||||
<input class="input-xxlarge" type="text" id="owner" ng-model="realm.owner">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions" ng-show="create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save</button>
|
||||
<button type="submit" ng-click="cancel()" class="btn" ng-click="cancel()" ng-show="changed">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions" ng-show="!create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save changes</button>
|
||||
<button type="submit" ng-click="reset()" class="btn" ng-show="changed">Clear changes</button>
|
||||
<a href="#/realms" ng-hide="changed">View realms »</a>
|
||||
<button type="submit" ng-click="remove()" class="btn btn-danger" ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,27 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/realm-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<a class="btn btn-small pull-right" href="#/realms/new">Add Realm</a>
|
||||
|
||||
<h1>
|
||||
<span class="gray">Realms</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Realm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="r in realms">
|
||||
<td><a href="#/realms/{{r.key}}">{{r.name}}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,18 @@
|
|||
<nav id="local-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li>
|
||||
<div>
|
||||
<span class="toggle">Realms</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-repeat="r in realms" ng-class="realm.key == r.key && 'active'">
|
||||
<a href=#/realms/{{r.key}}>{{r.name}}</a>
|
||||
<ul class="sub-items" ng-show="realm.key == r.key">
|
||||
<li ng-class="!path[2] && 'active'"><a href="#/realms/{{r.key}}">Configuration</a></li>
|
||||
<li ng-class="path[2] == 'users' && 'active'"><a href="#/realms/{{r.key}}/users">Users</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -0,0 +1,88 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/realm-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<h1>
|
||||
<span class="gray" ng-show="create">New User</span>
|
||||
<span class="gray" ng-hide="create">{{user.userId}}</span>
|
||||
</h1>
|
||||
|
||||
<div ng-show="userForm.showErrors && userForm.$error.required" class="alert alert-error">Please fill in all required fields</div>
|
||||
<p class="subtitle subtitle-right"><span class="required">*</span> Required fields</p>
|
||||
|
||||
<form class="form-horizontal" name="userForm" novalidate>
|
||||
<fieldset>
|
||||
<legend>Details</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="name">Username <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="name" name="name" ng-model="user.userId" autofocus required ng-readonly="!create">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="email">Email </label>
|
||||
<div class="controls">
|
||||
<input type="email" class="input-xlarge" id="email" name="email" ng-model="user.email">
|
||||
<span class="help-inline error" ng-show="userForm.showErrors && userForm.email.$invalid">Invalid email</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="firstName">Firstname </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="firstName" ng-model="user.firstName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="lastName">Lastname </label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-xlarge" id="lastName" ng-model="user.lastName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="password">Password <span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-xlarge" id="password" name="password" ng-model="user.password" ng-required="create">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="user.attributes.length > 0">
|
||||
<legend>Attributes</legend>
|
||||
|
||||
<table class="table table-striped table-bordered margin-top">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="attribute in user.attributes">
|
||||
<td><input type="text" placeholder="Name" value="{{attribute.name}}" readonly></td>
|
||||
<td><input type="text" placeholder="Value" value="{{attribute.value}}" readonly></td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions" ng-show="create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save</button>
|
||||
<button type="submit" ng-click="cancel()" class="btn" ng-click="cancel()" ng-show="changed">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions" ng-show="!create">
|
||||
<button type="submit" ng-click="save()" class="btn btn-primary" ng-show="changed">Save changes</button>
|
||||
<button type="submit" ng-click="reset()" class="btn" ng-show="changed">Clear changes</button>
|
||||
<a href="#/realms/{{realm.name}}/users" ng-hide="changed">View users »</a>
|
||||
<button type="submit" ng-click="remove()" class="btn btn-danger" ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<aside class="span3" ng-include src="'partials/realm-menu.html'"></aside>
|
||||
<div id="actions-bg"></div>
|
||||
|
||||
<div id="container-right" class="span9">
|
||||
<a class="btn btn-small pull-right" href="#/realms/{{realm.key}}/users/new">Add User</a>
|
||||
|
||||
<h1>
|
||||
<span class="gray">{{realm.name}}</span> users
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Firstname</th>
|
||||
<th>Lastname</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="user in users">
|
||||
<td><a href="#/realms/{{realm.key}}/users/{{user.userId}}">{{user.userId}}</a></td>
|
||||
<td>{{user.firstName}}</td>
|
||||
<td>{{user.lastName}}</td>
|
||||
<td>{{user.email}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|