Added admin ui from IdentityBroker
|
@ -25,6 +25,11 @@
|
||||||
<artifactId>keycloak-social</artifactId>
|
<artifactId>keycloak-social</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-ui</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<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>
|