Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit 2d55d83

Browse files
committed
gh-48 Implement bulk import of apps
* Add support to point to http resources * Add support to paste apps (properties) into a text-area field * Using a fileupload button, add the ability to load apps properties via a text-file * Add one-click ability to register out-of-the-box app starters (Stream + Task)
1 parent 4c3d2a4 commit 2d55d83

File tree

8 files changed

+337
-0
lines changed

8 files changed

+337
-0
lines changed

ui/app/scripts/app/controllers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,11 @@ define(['angular'], function (angular) {
3535
require(['app/controllers/register-apps'], function (registerAppsController) {
3636
$injector.invoke(registerAppsController, this, {'$scope': $scope});
3737
});
38+
}])
39+
.controller('BulkImportAppsController',
40+
['$scope', '$injector', function ($scope, $injector) {
41+
require(['app/controllers/bulk-import-apps'], function (bulkImportAppsController) {
42+
$injector.invoke(bulkImportAppsController, this, {'$scope': $scope});
43+
});
3844
}]);
3945
});

ui/app/scripts/app/controllers/apps.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,13 @@ define(['model/pageable'], function (Pageable) {
326326
$state.go('home.apps.registerApps');
327327
};
328328

329+
/**
330+
* Takes one to bulk import app page
331+
*/
332+
$scope.bulkImportApps = function() {
333+
$state.go('home.apps.bulkImportApps');
334+
};
335+
329336
$scope.$on('$destroy', function(){
330337
if ($scope.getAppDefinitions) {
331338
utils.$timeout.cancel($scope.getAppDefinitions);
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Definition bulk import apps controller.
19+
*
20+
* @author Gunnar Hillert
21+
*/
22+
define(function () {
23+
'use strict';
24+
return ['$scope', 'AppService', 'DataflowUtils', '$modal', '$state',
25+
function ($scope, appService, utils, $modal, $state) {
26+
27+
function newBulkAppImport() {
28+
return {
29+
uri: '',
30+
appsProperties: null,
31+
force: false
32+
};
33+
}
34+
35+
function newStreamAppStarters() {
36+
return [
37+
{
38+
name: 'Maven based Stream Applications with RabbitMQ Binder',
39+
uri: 'http://bit.ly/stream-applications-rabbit-maven',
40+
force: false
41+
},
42+
{
43+
name: 'Maven based Stream Applications with Kafka Binder',
44+
uri: 'http://bit.ly/stream-applications-kafka-maven',
45+
force: false
46+
},
47+
{
48+
name: 'Docker based Stream Applications with RabbitMQ Binder',
49+
uri: 'http://bit.ly/stream-applications-rabbit-docker',
50+
force: false
51+
},
52+
{
53+
name: 'Docker based Stream Applications with Kafka Binder',
54+
uri: 'http://bit.ly/stream-applications-kafka-docker',
55+
force: false
56+
}
57+
];
58+
}
59+
60+
function newTaskAppStarters() {
61+
return [
62+
{
63+
name: 'Maven based Task Applications',
64+
uri: 'http://bit.ly/task-applications-maven',
65+
force: false
66+
},
67+
{
68+
name: 'Docker based Task Applications',
69+
uri: 'http://bit.ly/task-applications-docker',
70+
force: false
71+
}
72+
];
73+
}
74+
75+
// Basic URI validation RegEx pattern
76+
$scope.uriPattern = '^([a-z0-9-]+:\/\/)([\\w\\.:-]+)(\/[\\w\\.:-]+)*$';
77+
78+
/**
79+
* Bulk Import Apps.
80+
*/
81+
$scope.bulkImportApps = function(item) {
82+
83+
if (item.uri && item.appsProperties) {
84+
utils.growl.error('Please provide only a URI or Properties not both.');
85+
return;
86+
}
87+
88+
if (item.uri) {
89+
console.log('Importing apps from ' + item.uri + ' (force: ' + item.force + ')');
90+
}
91+
if (item.appsProperties) {
92+
console.log('Importing apps using textarea values:\n' + item.appsProperties + ' (force: ' + item.force + ')');
93+
}
94+
95+
var promise;
96+
97+
promise = appService.bulkImportApps(item.uri, item.appsProperties, item.force).$promise;
98+
utils.addBusyPromise(promise);
99+
100+
promise.then(function() {
101+
utils.growl.success('Submitted bulk import request');
102+
console.log(newBulkAppImport());
103+
$scope.$watch('apps', function () {
104+
$scope.apps.appsProperties = '';
105+
$scope.apps.uri = '';
106+
$scope.apps.force = false;
107+
});
108+
$scope.streamAppStarters = newStreamAppStarters();
109+
$scope.taskAppStarters = newTaskAppStarters();
110+
});
111+
promise.catch(function(error) {
112+
utils.growl.error(error.data[0].message);
113+
});
114+
};
115+
116+
/**
117+
* Takes one to All Applications page
118+
*/
119+
$scope.close = function() {
120+
$state.go('home.apps.tabs.appsList');
121+
};
122+
123+
$scope.displayFileContents = function(contents) {
124+
console.log(contents);
125+
$scope.$watch('apps', function () {
126+
$scope.apps.appsProperties = contents.split('\n');
127+
});
128+
};
129+
130+
$scope.apps = newBulkAppImport();
131+
$scope.streamAppStarters = newStreamAppStarters();
132+
$scope.taskAppStarters = newTaskAppStarters();
133+
134+
$scope.$apply();
135+
136+
}];
137+
});

ui/app/scripts/app/services.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ define(['angular'], function (angular) {
7979
method: 'DELETE'
8080
}
8181
}).unregisterApp();
82+
},
83+
bulkImportApps: function(uri, appsProperties, force) {
84+
return $resource($rootScope.dataflowServerUrl + '/apps', {}, {
85+
bulkImportApps: {
86+
method: 'POST',
87+
params: {
88+
uri: uri,
89+
apps: appsProperties ? appsProperties.join('\n') : null,
90+
force: force ? true : false
91+
}
92+
}
93+
}).bulkImportApps();
8294
}
8395
};
8496
});

ui/app/scripts/app/views/apps-list.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
><span class="glyphicon glyphicon-trash"></span>
1515
Unregister Application(s)
1616
</button>
17+
<button id="bulkImportAppsButton" type="button" ng-click="bulkImportApps()"
18+
class="btn btn-default"
19+
><span class="glyphicon glyphicon-import"></span>
20+
Bulk Import Applications
21+
</button>
1722
</td>
1823
<td>
1924
<input type="text" class="form-control" ng-model="filterQuery" id="filterTable"
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<h1>Bulk Import Applications</h1>
2+
<p>
3+
Import and register applications in bulk. Simply provide a URI that points to
4+
the location of the <strong>properties file</strong> where the keys are formatted as
5+
<strong>type.name</strong> and the values are the URIs of the apps.
6+
For convenience, a list of out-of-the-box Stream and Task app starters is provided below, as well.
7+
</p>
8+
<hr/>
9+
10+
<form class="form-horizontal" name="bulkImportAppsForm" role="form" ng-submit="bulkImportApps(apps)" novalidate>
11+
<fieldset id="importAppsViaUri">
12+
<div class="form-group" ng-class="bulkImportAppsForm.uri.$invalid ? 'has-warning has-feedback' : ''">
13+
<label for="uri" class="col-sm-3 control-label">Uri</label>
14+
<div class="col-sm-7">
15+
<input type="text" id="uri" name="uri" autofocus
16+
class="form-control" placeholder="<http://url.to.properties>" ng-model="apps.uri" ng-pattern="uriPattern">
17+
<span class="glyphicon glyphicon-warning-sign form-control-feedback" ng-show="bulkImportAppsForm.uri.$invalid"></span>
18+
<p class="help-block">Please provide a valid URI pointing to the respective properties file.</p>
19+
</div>
20+
</div>
21+
</fieldset>
22+
<h2 class="text-center">OR</h2>
23+
<div class="row" style="margin-bottom: 1em;">
24+
<div class="col-md-6 col-md-offset-3">
25+
<p>
26+
Enter the list of properties into the text area field below. Alternatively, you can also select a
27+
file in your local file system, which is used to populate the text area field.
28+
</p>
29+
</div>
30+
</div>
31+
32+
<fieldset id="importAppsViaUpload">
33+
<div class="form-group" ng-class="bulkImportAppsForm.appsProperties.$invalid ? 'has-warning has-feedback' : ''">
34+
<label for="appsProperties" class="col-sm-3 control-label">Apps as Properties</label>
35+
<div class="col-sm-7">
36+
<textarea id="appsProperties" autofocus ng-model="apps.appsProperties" rows="5" ng-list="&#10;" ng-trim="false"
37+
class="form-control" placeholder="Example:
38+
task.timestamp=maven://org.springframework.cloud.task.app:timestamp-task:1.0.0.BUILD-SNAPSHOT
39+
task.spark-client=maven://org.springframework.cloud.task.app:spark-client-task:1.0.0.BUILD-SNAPSHOT"
40+
></textarea>
41+
<span class="glyphicon glyphicon-warning-sign form-control-feedback" ng-show="bulkImportAppsForm.appsProperties.$invalid"></span>
42+
<p class="help-block">Please provide a valid properties where the keys are formatted as
43+
<strong>type.name</strong> and the values are the URIs of the apps.</p>
44+
</div>
45+
</div>
46+
<div class="form-group" ng-class="bulkImportAppsForm.uri.$invalid ? 'has-warning has-feedback' : ''">
47+
<label for="uri" class="col-sm-3 control-label">Select Properties File</label>
48+
<div class="col-sm-7">
49+
<input type="file" on-read-file="displayFileContents(contents)" />
50+
<span class="glyphicon glyphicon-warning-sign form-control-feedback" ng-show="createDefinitionForm.definitionName.$invalid"></span>
51+
<p class="help-block">Please provide a text file containing properties. This will be used
52+
to populate the text area above.</p>
53+
</div>
54+
</div>
55+
</fieldset>
56+
57+
<div class="form-group">
58+
<div class="col-sm-7 col-sm-offset-3">
59+
<input type="checkbox" ng-model="apps.force" ng-model-options="{ getterSetter: true }" ui-indeterminate="someForceButNotAll()"/>
60+
Force
61+
<a dataflow-popover=".force-help-content" title="Force"><span class="glyphicon glyphicon-question-sign"></span></a>
62+
</div>
63+
</div>
64+
65+
<div class="row" style="margin-bottom: 2em;">
66+
<div class="col-md-6 text-right"><button id="back-button" type="button" class="btn btn-default" ng-click="close()">Cancel</button></div>
67+
<div class="col-md-6 text-left"><button id="submit-button" type="submit" class="btn btn-default" ng-disabled="bulkImportAppsForm.$invalid"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import</button></div>
68+
</div>
69+
</form>
70+
71+
<h2>Out-of-the-box Stream app-starters</h2>
72+
73+
<table class="table form-table">
74+
<col width="80%">
75+
<col width="10%">
76+
<col width="10%">
77+
<thead>
78+
<tr>
79+
<th>Name</th>
80+
<th class="text-center">Force <a dataflow-popover=".force-help-content" title="Force"><span class="glyphicon glyphicon-question-sign"></span></a></th>
81+
<th class="text-center">Action</th>
82+
</tr>
83+
</thead>
84+
<tbody>
85+
<tr ng-repeat="item in streamAppStarters">
86+
<td>
87+
{{item.name}}
88+
</td>
89+
<td class="text-center">
90+
<input type="checkbox" name="{{$index + '_force'}}" ng-model="item.force">
91+
</td>
92+
<td class="action-column text-center">
93+
<button type="button" ng-click="bulkImportApps(item)"
94+
class="btn btn-sm btn-default"
95+
title="Bulk Import Apps"
96+
><span class="glyphicon glyphicon-import"></span>
97+
</button>
98+
</td>
99+
</tr>
100+
</tbody>
101+
</table>
102+
103+
<h2>Out-of-the-box Task app-starters</h2>
104+
105+
<table class="table form-table">
106+
<col width="80%">
107+
<col width="10%">
108+
<col width="10%">
109+
<thead>
110+
<tr>
111+
<th>Name</th>
112+
<th class="text-center">Force <a dataflow-popover=".force-help-content" title="Force"><span class="glyphicon glyphicon-question-sign"></span></a></th>
113+
<th class="text-center">Action</th>
114+
</tr>
115+
</thead>
116+
<tbody>
117+
<tr ng-repeat="item in taskAppStarters">
118+
<td>
119+
{{item.name}}
120+
</td>
121+
<td class="text-center">
122+
<input type="checkbox" name="{{$index + '_force'}}" ng-model="item.force">
123+
</td>
124+
<td class="action-column text-center">
125+
<button type="button" ng-click="bulkImportApps(item)"
126+
class="btn btn-sm btn-default"
127+
title="Bulk Import Apps"
128+
><span class="glyphicon glyphicon-import"></span>
129+
</button>
130+
</td>
131+
</tr>
132+
</tbody>
133+
</table>
134+
135+
<div class="force-help-content hide">
136+
<span class="force-help-content">By checking <strong>force</strong>, the applications will be imported and installed
137+
even if it already exists but only if not being used already.</span>
138+
</div>

ui/app/scripts/directives.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,5 +359,28 @@ define(['angular', 'xregexp', 'moment'], function(angular) {
359359
};
360360
}
361361
};
362+
})
363+
.directive('onReadFile', function ($parse) {
364+
return {
365+
restrict: 'A',
366+
scope: false,
367+
link: function(scope, element, attrs) {
368+
element.bind('change', function() {
369+
370+
var onFileReadFn = $parse(attrs.onReadFile);
371+
var reader = new FileReader();
372+
373+
reader.onload = function() {
374+
var fileContents = reader.result;
375+
scope.$apply(function() {
376+
onFileReadFn(scope, {
377+
'contents' : fileContents
378+
});
379+
});
380+
};
381+
reader.readAsText(element[0].files[0]);
382+
});
383+
}
384+
};
362385
});
363386
});

ui/app/scripts/routes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,15 @@ define(['./app'], function (dashboard) {
369369
authenticate: true
370370
}
371371
})
372+
.state('home.apps.bulkImportApps', {
373+
url: 'apps/bulk-import-apps',
374+
templateUrl: appTemplatesPath + '/bulk-import-apps.html',
375+
controller: 'BulkImportAppsController',
376+
data: {
377+
title: 'Bulk Import Apps',
378+
authenticate: true
379+
}
380+
})
372381
.state('home.runtime.tabs', {
373382
url: 'runtime',
374383
abstract: true,

0 commit comments

Comments
 (0)