http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/instance.service.js ---------------------------------------------------------------------- diff --git a/src/app/instance/instance.service.js b/src/app/instance/instance.service.js new file mode 100644 index 0000000..6e809f4 --- /dev/null +++ b/src/app/instance/instance.service.js @@ -0,0 +1,485 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; +/*global $:false */ +/*global vkbeautify:false */ + +/** + * @ngdoc service + * @name angApp.InstanceService + * @description + * # InstanceService + * Service in the angApp. + */ +angular.module('odeConsole') + .factory('InstanceService', function ($log, $q, xmlFilter, SoapService, IMAPI_ENDPOINT) { + + var PMAPI_NS = 'http://www.apache.org/ode/pmapi'; + + /** private functions **/ + var nsResolver = function (prefix) { + var ns = { + 'ns' : 'http://www.apache.org/ode/pmapi/types/2006/08/02/', + 'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/', + 'pmapi' : 'http://www.apache.org/ode/pmapi' + }; + + return ns[prefix] || null; + }; + + var mapInstanceInfo = function (instanceInfoElement) { + var processNameEl = instanceInfoElement.xpath('ns:process-name', nsResolver), + rootScopeEl = instanceInfoElement.xpath('ns:root-scope', nsResolver), + iObj = { + iid: instanceInfoElement.xpath('ns:iid', nsResolver).text(), + pid: instanceInfoElement.xpath('ns:pid', nsResolver).text(), + processNameShort: processNameEl.text(), + processNameFull: SoapService.qnameToString(SoapService.createQNameObj(processNameEl.text(), processNameEl[0])), + processQName: SoapService.createQNameObj(processNameEl.text(), processNameEl[0]), + status: instanceInfoElement.xpath('ns:status', nsResolver).text(), + started: instanceInfoElement.xpath('ns:dt-started', nsResolver).text(), + lastActive: instanceInfoElement.xpath('ns:dt-last-active', nsResolver).text(), + rootScope: {id: rootScopeEl.attr('siid'), + name: rootScopeEl.attr('name'), + status: rootScopeEl.attr('status'), + modelId: rootScopeEl.attr('modelId')} + }; + + if (instanceInfoElement.xpath('ns:fault-info', nsResolver).length !== 0) { + var faultNameEl = instanceInfoElement.xpath('ns:fault-info/ns:name', nsResolver); + iObj.fault = { + name: SoapService.createQNameObj(faultNameEl.text(), faultNameEl[0]), + explanation: instanceInfoElement.xpath('ns:fault-info/ns:explanation', nsResolver).text(), + lineNumber: instanceInfoElement.xpath('ns:fault-info/ns:line-number', nsResolver).text(), + aiid: instanceInfoElement.xpath('ns:fault-info/ns:aiid', nsResolver).text() + }; + } + + if (instanceInfoElement.xpath('ns:failures', nsResolver).length !== 0) { + iObj.failures = { + failure: instanceInfoElement.xpath('ns:failures/ns:dt-failure', nsResolver).text(), + count: instanceInfoElement.xpath('ns:failures/ns:count', nsResolver).text() + }; + } + + if (instanceInfoElement.xpath('ns:correlation-properties/ns:correlation-property', nsResolver).length !== 0) { + var cProps = instanceInfoElement.xpath('ns:correlation-properties/ns:correlation-property', nsResolver); + iObj.correlationProperties = []; + for (var j = 0; j < cProps.length; j++) { + var propEl = angular.element(cProps[j]); + iObj.correlationProperties.push( + { name: SoapService.createQNameObj(propEl.attr('propertyName'), propEl[0]), + value: propEl.text() + }); + } + } + + return iObj; + }; + + var mapScopeInfo = function (scopeInfoElement) { + var si = {}; + si.siid = scopeInfoElement.xpath('ns:siid', nsResolver).text(); + si.name = scopeInfoElement.xpath('ns:name', nsResolver).text(); + si.status = scopeInfoElement.xpath('ns:status', nsResolver).text(); + + // variables + si.variables = []; + scopeInfoElement.xpath('ns:variables/ns:variable-ref', nsResolver).each(function() { + var vr = {}; + vr.iid = Number($(this).attr('iid')); + vr.siid = Number($(this).attr('siid')); + vr.name = $(this).attr('name'); + vr.scope = { name: si.name, status: si.status }; + + si.variables.push(vr); + }); + + // endpoints + si.endpoints = []; + scopeInfoElement.xpath('ns:endpoints/ns:endpoint-ref', nsResolver).each(function() { + var er = {}; + er.siid = si.siid; + er.partnerLink = $(this).attr('partner-link'); + er.partnerRole = $(this).attr('partner-role'); + er.scope = { name: si.name, status: si.status }; + er.value = vkbeautify.xml($(this).first().html().trim(), 2); + si.endpoints.push(er); + }); + + // activities + si.activities = []; + scopeInfoElement.xpath('ns:activities/ns:activity-info', nsResolver).each(function() { + var act = {}; + act.name = $(this).xpath('ns:name', nsResolver).text(); + act.type = $(this).xpath('ns:type', nsResolver).text(); + act.status = $(this).xpath('ns:status', nsResolver).text(); + act.enabled = $(this).xpath('ns:dt-enabled', nsResolver).text(); + act.started = $(this).xpath('ns:dt-started', nsResolver).text(); + act.completed = $(this).xpath('ns:dt-completed', nsResolver).text(); + act.aiid = Number($(this).xpath('ns:aiid', nsResolver).text()); + var scope = $(this).xpath('ns:scope', nsResolver); + act.scope = {siid : Number(scope.attr('siid')), name: scope.attr('name'), modelId: Number(scope.attr('modelId')), status: si.status }; + si.activities.push(act); + + var failure = $(this).xpath('ns:failure', nsResolver); + if (failure.length > 0) { + act.failure = {}; + act.failure.failure = failure.xpath('ns:dt-failure', nsResolver).text(); + act.failure.retries = failure.xpath('ns:retries', nsResolver).text(); + act.failure.reason = failure.xpath('ns:reason', nsResolver).text(); + act.failure.actions = failure.xpath('ns:actions', nsResolver).text().split(' '); + } + }); + + // children + si.children = []; + // add child references + scopeInfoElement.xpath('ns:children/ns:child-ref', nsResolver).each(function() { + var cr = {}; + cr.siid = Number($(this).attr('siid')); + cr.name = $(this).attr('name'); + cr.status = $(this).attr('status'); + cr.modelId = Number($(this).attr('modelId')); + cr.isRef = true; + + si.children.push(cr); + }); + + return si; + }; + + /** public functions **/ + var is = {}; + + /** + * List instances and only return summary information about the instance, + * combined with all correlation properties. + * + * The request identifies the process instances using a filter that can + * select instances with a given name, status, property values, etc. + * Without a filter, the operation returns all process instances up to a + * specified <code>limit<.code>. The request also indicates which key fields + * to use for ordering the results. + * </p> + * + * <p> + * The filter element can be used to narrow down the list of process definitions + * by applying selection criteria. There are six filters that can be applied: + * <ul> + * <li><p>name -- Only process instances with this local name.</p></li> + * <li><p>namespace -- Only process instances with this namespace URI.</p></li> + * <li><p>status -- Only process instances with these status code(s).</p></li> + * <li><p>started -- Only process instances started relative to this date/time.</p></li> + * <li><p> last-active -- Only process instances last active relative to this date/time.</p></li> + * <li><p>$property -- Only process instances with a correlation property equal to the + * specified value.</p></li> + * </ul> + * + * </p> + * <p> + * The name and namespace filters can do full or partial name matching. Partial matching + * occurs if either filter ends with an asterisk (*). These filters are not case sensitive, + * for example name=my* will match MyProcess and my-process. If unspecified, the default + * filter is name=* namespace=*. + * </p> + * + * <p> + * The status filter can be used to filter all process definitions based on six status codes: + * <ul> + * <li><p>active -- All currently active process instances (excludes instances in any other + * state).</p></li> + * <li><p>suspended -- All process instances that have not completed, but are currently + * suspended.</p></li> + * <li><p>error -- All process instances that have not completed, but are currently indicate an + * error condition.</p></li> + * <li><p>completed -- All successfully completed process instances (excludes instances in any + * other state). </p></li> + * <li><p>terminated -- All process instances that were terminated. + * <li><p>faulted -- All process instances that encountered a fault (in the global scope). + * </ul> + * <p> + * The started filter can be used to filter all process instances started on or after a + * particular date or date/time instant. The value of this filter is either an ISO-8601 + * date or ISO-8601 date/time. For example, to find all process instances started on or + * after September 1, 2005, use started>=20050901. Similarly, the last-active filter can + * be used to filter all process instances based on their last active time. The last + * active time records when the process last completed performing work, and either + * completed or is now waiting to receive a message, a timeout or some other event. + * </p> + * + * <p> + * Each process instance has one or more properties that are set its instantiation, that + * can be used to distinguish it from other process instances. In this version of the + * specification, we only support properties instantiated as part of correlation sets + * defined in the global scope of the process. For example, if a process instantiates a + * correlation set that uses the property order-id, it is possible to filter that process + * instance based on the value of that property. + * </p> + * + * <p> + * The property name is identified by the prefix $. If the property name is an NCName, + * the filter will match all properties with that local name. If the property name is + * {namespace}local, the filter will match all properties with the specified namespace URI + * and local name. For example, to retrieve a list of all active process instances with a + * property order-id that has the value 456, use status=active $order-id=456. + * </p> + * + * <p> + * By default the response returns process instances in no particular order. The order + * element can be used to order the results by specifying a space-separated list of keys. + * Each key can be prefixed with a plus sign '+' to specify ascending order, or a '-' + * minus sign to specify descending order. Without a sign the default behavior is to + * return process instances in ascending order. The currently supported odering keys are: + * <ul> + * <li><p>pid</p></li> -- Order based on the process identifier. + * <li><p>name</p></li> -- Order based on the local name of the process instance. + * <li><p>namespace</p></li> -- Order based on the namespace URI of the process instance. + * <li><p>version</p></li> -- Order based on the version number. + * <li><p>status</p></li> -- Order based on the status of the process instance. + * <li><p>started</p></li> -- Order based on the process instance start date/time. + * <li><p>last-active</p></li> -- Order based on the process instance last active date/time. + * </ul> + * + * @param {string} filter See listInstances' filter argument + * @param {string} order See listInstances' order argument + * @param {number} limit maximum number of instances to return + * @returns list of matching instances + */ + is.listInstancesSummary = function (filter, order, limit) { + filter = filter || ''; + order = order || ''; + limit = limit || 0; + + var deferred = $q.defer(); + + SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'listInstancesSummary', namespaceURI: PMAPI_NS}, + {filter: filter, order: order, limit: limit}) + .then(function(response) { + var instances = [], + els = response.xpath('//ns:instance-info', nsResolver), + instance, + i; + + for (i = 0; i < els.length; i += 1) { + instance = angular.element(els[i]); + instances.push(mapInstanceInfo(instance)); + } + + deferred.resolve(instances); + + }, function(fault) { + deferred.reject(fault); + }); + + return deferred.promise; + }; + + /** + * Get an instance by id. + * @param {number} iid + * @returns information about a specific instance + */ + is.getInstanceInfo = function (iid) { + var deferred = $q.defer(); + + SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'getInstanceInfo', namespaceURI: PMAPI_NS}, + {iid: iid}) + .then(function(response) { + deferred.resolve(mapInstanceInfo(response.xpath('//instance-info', nsResolver))); + }, function(fault) { + deferred.reject(fault); + }); + + return deferred.promise; + }; + + /** Get info about a scope instance by id, optionally including activity info. + * @param {number} siid scope instance identifier + * @param {boolean} activityInfo if <code>true</code>, include activity info + * @returns information about a specific scope instance + */ + is.getScopeInfoWithActivity = function (siid, activityInfo) { + var deferred = $q.defer(); + + SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'getScopeInfoWithActivity', namespaceURI: PMAPI_NS}, + {sid: siid, activityInfo: activityInfo}) + .then(function(response) { + deferred.resolve(mapScopeInfo(response.xpath('//scope-info'))); + }, function(fault) { + deferred.reject(fault); + }); + + return deferred.promise; + }; + + /** + * Delete a process instances with the given IID. + * @param iid InstanceID. + * @returns collection of instances identfiers, corresponding to deleted + * instances + */ + is.delete = function (iid) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'delete', namespaceURI: PMAPI_NS}, + {filter: 'iid = ' + iid}); + }; + + /** + * Delete the process instances matching the given filter. + * @param filter instance filter (see listInstancesSummary ). + * @returns collection of instances identfiers, corresponding to deleted + * instances + */ + is.deleteFilter = function (filter) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'delete', namespaceURI: PMAPI_NS}, + {filter: filter}); + }; + + /** + * Causes the process instance to terminate immediately, without a chance to + * perform any fault handling or compensation. The process transitions to the + * terminated state. It only affects process instances that are in the active, + * suspended or error states. + * @param iid instance id + * @returns post-change instance information + */ + is.terminate = function (iid) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'terminate', namespaceURI: PMAPI_NS}, + {iid: iid}); + }; + + /** + * Changes the process state from active to suspended. this affects process instances that + * are in the active or error states. + * @param iid instance id + * @returns post-change instance information + */ + is.suspend = function (iid) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'suspend', namespaceURI: PMAPI_NS}, + {iid: iid}); + }; + + /** + * Resume the (previously suspended) instance. This operation only affects process instances + * that are in the suspended state. + * @param iid instance id + * @returns post-change instance information + */ + is.resume = function (iid) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'resume', namespaceURI: PMAPI_NS}, + {iid: iid}); + }; + + /** + * Performs an activity recovery action. + * @param iid instance id (process) + * @param aid instance id (activity) + * @param action recovery action (e.g. retry, fault) + * @returns post-change instance information + */ + is.recoverActivity = function (iid, aid, action) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'recoverActivity', namespaceURI: PMAPI_NS}, + {iid: iid, aid: aid, action: action}); + }; + + /** + * Get info about a variable. + * @param siid scope identifier + * @param varName variable name + * @returns information about variable (basically the value) + */ + is.getVariableInfo = function (siid, varName) { + var deferred = $q.defer(); + + SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'getVariableInfo', namespaceURI: PMAPI_NS}, + {sid: siid, varName: varName}) + .then(function(response) { + var res = {}; + var selfEl = response.xpath('//variable-info/ns:self', nsResolver); + res.name = selfEl.attr('name'); + res.iid = selfEl.attr('iid'); + res.siid = selfEl.attr('siid'); + res.value = response.xpath('//variable-info/ns:value', nsResolver).html().trim(); + deferred.resolve(res); + }, function(fault) { + deferred.reject(fault); + }); + + return deferred.promise; + }; + + is.setVariable = function (siid, varName, value) { + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'setVariable', namespaceURI: PMAPI_NS}, + {sid: siid, varName: varName, value: value}); + }; + + /** + * Retrieve a timeline of BPEL events. + * + * @param instanceFilter instance filter (if set,return only events for matching instances) + * @param eventFilter event filter (event type and data range) + * @returns list of stringified dates (in ISO format) + */ + is.getEventTimeline = function (instanceFilter, eventFilter) { + instanceFilter = instanceFilter || ''; + eventFilter = eventFilter || ''; + + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'getEventTimeline', namespaceURI: PMAPI_NS}, + {instanceFilter: instanceFilter, eventFilter: eventFilter}); + //TODO + }; + + /** + * Retrieve BPEL events. One may specify an "instance filter" and an "event filter" to + * limit the number of events returned. The instance filter takes the exact same syntax + * as for the {@link #listInstances(String, String, int)} method. The "event filter" employs + * a similar syntax; the following properties may be filtered: <ol> + * <li><em>type</em> - the event type</li> + * <li><em>tstamp</em> - the event timestamp</li> + * </ol> + * @param instanceFilter instance filter (if set,return only events for matching instances) + * @param eventFilter event filter (event type and data range) + * @returns list of events + */ + is.listEvents = function (instanceFilter, eventFilter, maxCount) { + instanceFilter = instanceFilter || ''; + eventFilter = eventFilter || ''; + + return SoapService.callSOAP(IMAPI_ENDPOINT, + {localName: 'getEventTimeline', namespaceURI: PMAPI_NS}, + {instanceFilter: instanceFilter, eventFilter: eventFilter, maxCount: maxCount}); + //TODO + }; + + return is; + +});
http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/instanceactionbuttons.html ---------------------------------------------------------------------- diff --git a/src/app/instance/instanceactionbuttons.html b/src/app/instance/instanceactionbuttons.html new file mode 100644 index 0000000..37468a4 --- /dev/null +++ b/src/app/instance/instanceactionbuttons.html @@ -0,0 +1,14 @@ +<div ng-controller="InstanceActionsController"> +<button type="button" class="btn btn-default btn-xs" ng-if="instance.status == 'SUSPENDED'" ng-click="resume(instance.iid)"> + <span class="glyphicon glyphicon-play"></span> Resume +</button> +<button type="button" class="btn btn-default btn-xs" ng-if="instance.status == 'ACTIVE'" ng-click="suspend(instance.iid)"> + <span class="glyphicon glyphicon-pause"></span> Suspend +</button> +<button type="button" class="btn btn-xs btn-warning" ng-if="instance.status == 'ACTIVE' || instance.status == 'SUSPENDED'" ng-really-message="Are you sure?" ng-really-click="terminate(instance.iid)"> + <span class="glyphicon glyphicon-stop"></span> Terminate +</button> +<button type="button" class="btn btn-xs btn-danger" ng-really-message="Are you sure?" ng-really-click="delete(instance.iid)"> + <span class="glyphicon glyphicon-remove"></span> Delete +</button> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/instanceactions.controller.js ---------------------------------------------------------------------- diff --git a/src/app/instance/instanceactions.controller.js b/src/app/instance/instanceactions.controller.js new file mode 100644 index 0000000..16625d6 --- /dev/null +++ b/src/app/instance/instanceactions.controller.js @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('odeConsole') + .controller('InstanceActionsController', function ($scope, $location, xmlParser, $http, InstanceService, ngToast, $modal, $q) { + + var fireChangeEvent = function (action, iids) { + $scope.$emit('instance-modified', {action: action, iids: iids}); + }; + + $scope.delete = function(iids) { + iids = iids.pop ? iids : [iids]; + if (iids.length === 0) {return;} + console.log('delete ' + iids); + + $q.all(iids.map(function (id) { + return InstanceService.delete(id); + })).then(function() { + fireChangeEvent('delete', iids); + }); + }; + + $scope.terminate = function(iids) { + iids = iids.pop ? iids : [iids]; + if (iids.length === 0) {return;} + console.log('terminate ' + iids); + + $q.all(iids.map(function (id) { + return InstanceService.terminate(id); + })).then(function() { + fireChangeEvent('terminate', iids); + }); + }; + + $scope.suspend = function(iids) { + iids = iids.pop ? iids : [iids]; + if (iids.length === 0) {return;} + console.log('suspend ' + iids); + + $q.all(iids.map(function (id) { + return InstanceService.suspend(id); + })).then(function() { + fireChangeEvent('suspend', iids); + }); + }; + + $scope.resume = function(iids) { + iids = iids.pop ? iids : [iids]; + if (iids.length === 0) {return;} + console.log('resume ' + iids); + + $q.all(iids.map(function (id) { + return InstanceService.resume(id); + })).then(function() { + fireChangeEvent('resume', iids); + }); + }; + + $scope.deleteAll = function() { + console.log('deleteAll ' + $scope.filter); + InstanceService.deleteFilter($scope.filter).then(function() { + fireChangeEvent('deleteAll', '*'); + }); + }; + + $scope.openFaultModal = function () { + $modal.open({ + templateUrl: 'app/instance/faultinfodialog.html', + scope: $scope, + }); + }; + + $scope.openCorrelationPropertiesModal = function () { + $modal.open({ + templateUrl: 'app/instance/corrpropinfodialog.html', + scope: $scope, + }); + }; +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/instancelist.controller.js ---------------------------------------------------------------------- diff --git a/src/app/instance/instancelist.controller.js b/src/app/instance/instancelist.controller.js new file mode 100644 index 0000000..b27ecc6 --- /dev/null +++ b/src/app/instance/instancelist.controller.js @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('odeConsole') + .controller('InstanceListController', function ($scope, $location, xmlParser, $http, InstanceService, ngToast) { + + var updateTable = function () { + InstanceService.listInstancesSummary($scope.filter, '-last-active', 200).then(function(instances) { + $scope.instances = instances; + }, function(fault) { + $scope.instances = []; + var re = /java\.lang\.RuntimeException: Invocation of method \S+ in management interface failed: (.+)/, + m; + if ((m = re.exec(fault.faultstring)) !== null) { + ngToast.create({content: m[0], class: 'warning'}); + } else { + ngToast.create({content: 'Could not load process instance list.', class: 'danger'}); + } + }); + }; + + $scope.getSelectedIIDs = function () { + return $scope.instances.filter(function (i) { + return i.isSelected; + }).map(function (i) { + return i.iid; + }); + }; + + // set filter to query or empty string + $scope.filter = ($location.search()).q || ''; + // watch query to update filter and table + $scope.$watch(function () { return $location.search(); }, function() { + $scope.filter = ($location.search()).q || ''; + updateTable(); + }); + + + $scope.update = function () { + $scope.loaded = false; + $location.search({q: $scope.filter}); + }; + + $scope.instances = []; + $scope.itemsByPage = 25; + + // listen for changes issued by actions to reload table + $scope.$on('instance-modified', function () { + updateTable(); + }); + +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/instancelist.html ---------------------------------------------------------------------- diff --git a/src/app/instance/instancelist.html b/src/app/instance/instancelist.html new file mode 100644 index 0000000..981102c --- /dev/null +++ b/src/app/instance/instancelist.html @@ -0,0 +1,78 @@ +<div class="container"> + + <div class="panel panel-default" st-table="instancesViewModel" st-safe-src="instances"> + <!-- Default panel contents --> + <div class="panel-heading"> + <form class="" role="search" ng-submit="update()"> + <div class="input-group"> + <input type="text" class="form-control" ng-model="filter" autofocus=""> + <span class="input-group-btn"> + <button class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search"></span></button> + </span> + </div> + </form> + </div> + <table class="table table-condensed"> + <thead> + <tr> + <th><select-all-checkbox items="instancesViewModel" prop="isSelected"></ui-select-all></th> + <th st-sort="iid">IID</th> + <th st-sort="processNameShort">Process name</th> + <th st-sort="status">Status</th> + <th st-sort="started">Started</th> + <th st-sort="lastActive" st-sort-default="reverse">Last Active</th> + <th>Action</th> + </tr> + </thead> + <tbody> + <tr ng-if="!instances.length"> + <td colspan="7" ng-if="!instances.length"> + <h4 class="text-center">No instances found.</h4> + </td> + </tr> + <tr ng-class="instance.status | lowercase" ng-repeat="instance in instancesViewModel"> + <td><input type="checkbox" ng-model="instance.isSelected"></td> + <td><a href="#/instances/{{instance.iid}}">{{instance.iid}}</a></td> + <td data-toggle="tooltip" title="{{instance.processNameFull}}"> + <a href="#/processes/{{instance.pid}}">{{instance.pid}}</a> + </td> + <td ng-controller="InstanceActionsController"> + <span class="label ng-class: 'label-instance-' + (instance.status | lowercase);">{{instance.status}}</span> + <i class="fa fa-exclamation-circle text-info" style="vertical-align:middle" aria-hidden="true" ng-if="instance.fault" ng-click="openFaultModal()" tooltip="Show fault details"></i> + <i class="fa fa-bullseye" style="vertical-align:middle" aria-hidden="true" ng-if="instance.correlationProperties" ng-click="openCorrelationPropertiesModal()" tooltip="Show correlation properties"></i> + <i class="fa fa-flash" style="vertical-align:middle" aria-hidden="true" ng-if="instance.failures" tooltip="{{instance.failures.count}} failure(s) @ {{instance.failures.failure | date:'medium'}}"></i> + </td> + <td> + <time datetime="{{instance.started}}" tooltip="{{instance.started | date:'medium'}}" am-time-ago="instance.started"></time> + </td> + <td> + <time datetime="{{instance.lastActive}}" tooltip="{{instance.lastActive | date:'medium'}}" am-time-ago="instance.lastActive"></time> + </td> + <td><div ng-include="'app/instance/instanceactionbuttons.html'"></div></td> + </tr> + </tbody> + <tfoot> + <tr> + <td colspan="7"> + <div class="pull-left" ng-controller="InstanceActionsController"> + <button type="button" class="btn btn-default btn-sm" ng-click="suspend(getSelectedIIDs())"><span class="glyphicon glyphicon-pause"></span> Suspend</button> + <button type="button" class="btn btn-default btn-sm" ng-click="resume(getSelectedIIDs())"><span class="glyphicon glyphicon-play"></span> Resume</button> + <button type="button" class="btn btn-warning btn-sm" ng-really-message="Are you sure?" ng-really-click="terminate(getSelectedIIDs())"><span class="glyphicon glyphicon-stop"></span> Terminate</button> + <button type="button" class="btn btn-danger btn-sm" ng-really-message="Are you sure?" ng-really-click="delete(getSelectedIIDs())"><span class="glyphicon glyphicon-remove"></span> Delete</button> + <button type="button" class="btn btn-danger btn-sm" ng-really-message="Are you sure?" ng-really-click="deleteAll()"><span class="glyphicon glyphicon-remove"></span> Delete All</button> + </div> + <div class="btn-group pull-right"> + <label class="btn btn-default btn-xs" ng-model="itemsByPage" btn-radio="25" uncheckable>25</label> + <label class="btn btn-default btn-xs" ng-model="itemsByPage" btn-radio="50" uncheckable>50</label> + <label class="btn btn-default btn-xs" ng-model="itemsByPage" btn-radio="100" uncheckable>100</label> + </div> + </td> + </tr> + </tfoot> + </table> + <div class="panel-footer text-center"> + <div st-pagination="" st-items-by-page="itemsByPage" st-displayed-pages="7"></div> + </div> + </div> + +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/recoverydialog.html ---------------------------------------------------------------------- diff --git a/src/app/instance/recoverydialog.html b/src/app/instance/recoverydialog.html new file mode 100644 index 0000000..1399827 --- /dev/null +++ b/src/app/instance/recoverydialog.html @@ -0,0 +1,55 @@ +<div class="modal-header"> + <h3 class="modal-title">Activity Recovery</h3> +</div> +<div class="modal-body"> + <p>The following activity has entered a failure state. It can be manually recovered.</p> + <table class="table table-condensed"> + <tbody> + <tr> + <th>Name</th> + <td>{{activity.name}}</td> + </tr> + <tr> + <th>Type</th> + <td>{{activity.type}}</td> + </tr> + <tr> + <th>Status</th> + <td>{{activity.status}}</td> + </tr> + <tr> + <th>Enabled</th> + <td>{{activity.enabled}}</td> + </tr> + <tr> + <th>Started</th> + <td>{{activity.started}}</td> + </tr> + <tr> + <th>Failure</th> + <td>{{activity.failure.failure}}</td> + </tr> + <tr> + <th>Retries</th> + <td>{{activity.failure.retries}}</td> + </tr> + <tr> + <th>Reason</th> + <td>{{activity.failure.reason}}</td> + </tr> + </tbody> + </table> + + <h4>Recovery Options</h4> + <div class="btn-group"> + <label style="text-transform: capitalize" ng-repeat="action in activity.failure.actions" class="btn btn-default" ng-model="radio.model" btn-radio="action">{{action}}</label> + </div> + <p ng-if="radio.model == 'retry'">The execution of the activity will be tried again.</p> + <p ng-if="radio.model == 'cancel'">The execution of the activity will be cancelled.</p> + <p ng-if="radio.model == 'fault'">The execution of the activity will be faulted. The process instance will either handle this fault in a fault handler or will enter a fault state itself.</p> + +</div> +<div class="modal-footer"> + <button class="btn btn-primary" ng-disabled="!radio.model" ng-click="$close()">OK</button> + <button class="btn btn-default" ng-click="$dismiss()">Cancel</button> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/instance/varinfodialog.html ---------------------------------------------------------------------- diff --git a/src/app/instance/varinfodialog.html b/src/app/instance/varinfodialog.html new file mode 100644 index 0000000..e9e65c2 --- /dev/null +++ b/src/app/instance/varinfodialog.html @@ -0,0 +1,15 @@ +<div class="modal-header"> + <h3 class="modal-title">Edit Variable Details</h3> +</div> +<div class="modal-body"> + <h4>Variable Name</h4> + <p>{{var.name}}</p> + <h4>Content</h4> + <div ui-ace="{mode: 'xml'}" ng-model="var.value"></div> + <h4>Scope Instance ID</h4> + <p>{{var.siid}}</p> +</div> +<div class="modal-footer"> + <button class="btn btn-primary" ng-click="$close(var)">OK</button> + <button class="btn btn-default" ng-click="$dismiss()">Cancel</button> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/process.controller.js ---------------------------------------------------------------------- diff --git a/src/app/process/process.controller.js b/src/app/process/process.controller.js new file mode 100644 index 0000000..1ed3af5 --- /dev/null +++ b/src/app/process/process.controller.js @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('odeConsole') + .controller('ProcessController', function ($scope, $routeParams, xmlParser, $http, ProcessService, InstanceService, ngToast) { + + var updateInstanceTable = function () { + InstanceService.listInstancesSummary('pid = ' + $routeParams.pid, '-last-active', $scope.maxInstances).then(function(instances) { + $scope.instances = instances; + }, function(fault) { + $scope.instances = []; + var re = /java\.lang\.RuntimeException: Invocation of method \S+ in management interface failed: (.+)/, + m; + if ((m = re.exec(fault.faultstring)) !== null) { + ngToast.create({content: m[0], class: 'warning'}); + } else { + ngToast.create({content: 'Could not load process instance list.', class: 'danger'}); + } + }); + }; + + var updateProcessInfo = function () { + ProcessService.getProcessInfo($routeParams.pid).then(function(process) { + $scope.process = process; + }, function() { + ngToast.create({content: 'Could not load process ' + $routeParams.pid + '.', class: 'danger'}); + }); + }; + + // listen for changes issued by actions to reload table + $scope.$on('instance-modified', function () { + updateInstanceTable(); + }); + $scope.$on('process-modified', function () { + updateProcessInfo(); + }); + + $scope.maxInstances = 15; + + + updateInstanceTable(); + updateProcessInfo(); +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/process.html ---------------------------------------------------------------------- diff --git a/src/app/process/process.html b/src/app/process/process.html new file mode 100644 index 0000000..b596bba --- /dev/null +++ b/src/app/process/process.html @@ -0,0 +1,99 @@ +<div class="container"> + + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Process Details</h3> + </div> + <table class="table"> + <tr> + <th>Process Name</th> + <td><a href="#/instances?q=name%20%3D%20{{process.nameFull | escape}}">{{process.nameFull}}</a></td> + </tr> + <tr> + <th>Process ID</th> + <td>{{process.pid}}</td> + </tr> + <tr> + <th>Package</th> + <td>{{process.package}}</td> + </tr> + <tr> + <th>Deploy Date</th> + <td><span tooltip="{{process.deployDate | amCalendar}}">{{process.deployDate | date:'medium'}}</span></td> + </tr> + <tr> + <th>Status</th> + <td><span class="label ng-class: 'label-process-' + (process.status | lowercase);">{{process.status}}</span></td> + </tr> + <tr> + <th>WSDL URL</th> + <td></td> + </tr> + <tr> + <th>Actions</th> + <td><div ng-include="'app/process/processactionbuttons.html'"></div></td> + </tr> + </table> + </div> + + <div class="panel panel-default" ng-if="process.endpoints.length"> + <div class="panel-heading"> + <h3 class="panel-title">Endpoints</h3> + </div> + <ul class="list-group"> + <li class="list-group-item" ng-repeat="endpoint in process.endpoints"> + <div ui-ace="{mode: 'xml'}" readonly ng-model="endpoint"></div> + </li> + </ul> + </div> + + <div class="panel panel-default" ng-cloak> + <div class="panel-heading"> + <h3 class="panel-title"><span tooltip="Top {{maxInstances}} instances, ordered by last activity">Most Recent Process Instances</span></h3> + </div> + <table class="table table-condensed"> + <thead> + <tr> + <th>IID</th> + <th>Process name</th> + <th>Status</th> + <th>Started</th> + <th>Last Active</th> + <th>Action</th> + </tr> + </thead> + <tbody> + <tr ng-if="!instances.length"> + <td colspan="6" ng-if="!instances.length"> + <h4 class="text-center">No instances found.</h4> + </td> + </tr> + <tr ng-class="instance.status | lowercase" ng-repeat="instance in instances"> + <td><a href="#/instances/{{instance.iid}}">{{instance.iid}}</a></td> + <td data-toggle="tooltip" title="{{instance.processNameFull}}"> + <a href="#/processes/{{instance.pid}}">{{instance.pid}}</a> + </td> + <td ng-controller="InstanceActionsController"> + <span class="label ng-class: 'label-instance-' + (instance.status | lowercase);">{{instance.status}}</span> + <i class="fa fa-exclamation-circle text-info" style="vertical-align:middle" aria-hidden="true" ng-if="instance.fault" ng-click="openFaultModal()" tooltip="Show fault details"></i> + <i class="fa fa-bullseye" style="vertical-align:middle" aria-hidden="true" ng-if="instance.correlationProperties" ng-click="openCorrelationPropertiesModal()" tooltip="Show correlation properties"></i> + <i class="fa fa-flash" style="vertical-align:middle" aria-hidden="true" ng-if="instance.failures" tooltip="{{instance.failures.count}} failure(s) ({{instance.failures.failure}})"></i> + </td> + <td> + <time datetime="{{instance.started}}" tooltip="{{instance.started | date:'medium'}}" am-time-ago="instance.started"></time> + </td> + <td> + <time datetime="{{instance.lastActive}}" tooltip="{{instance.lastActive | date:'medium'}}" am-time-ago="instance.lastActive"></time> + </td> + <td><div ng-include="'app/instance/instanceactionbuttons.html'"></div></td> + </tr> + </tbody> + <tfoot> + <tr> + <td colspan="6"><a href="#/instances?q={{'pid = ' + process.pid | escape}}">Show all instances</a> + </td> + </tr> + </tfoot> + </table> + </div> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/process.service.js ---------------------------------------------------------------------- diff --git a/src/app/process/process.service.js b/src/app/process/process.service.js new file mode 100644 index 0000000..938e2c1 --- /dev/null +++ b/src/app/process/process.service.js @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; +/*global $:false */ +/*global vkbeautify:false */ + +/** + * @ngdoc service + * @name angApp.InstanceService + * @description + * # InstanceService + * Service in the angApp. + */ +angular.module('odeConsole') + .factory('ProcessService', function ($http, $log, $q, $interval, _, SoapService, PMAPI_ENDPOINT, DSAPI_ENDPOINT, POLLING_INTERVAL) { + + var PMAPI_NS = 'http://www.apache.org/ode/pmapi'; + + /** private functions **/ + var nsResolver = function (prefix) { + var ns = { + 'ns' : 'http://www.apache.org/ode/pmapi/types/2006/08/02/', + 'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/', + 'pmapi' : 'http://www.apache.org/ode/pmapi' + }; + + return ns[prefix] || null; + }; + + var splitPackageName = function(packageName) { + var split = packageName.split('-'); + var hasVersion = !isNaN(_.last(split)); + var result = { + packageId: packageName, + name: (_.first(split, split.length - hasVersion).join('-')), + }; + + result.version = hasVersion ? _.last(split) : null; + return result; + }; + + var mapProcessInfo = function(processElement) { + var processNameEl = processElement.xpath('ns:definition-info/ns:process-name', nsResolver); + var result = {}; + result.pid = processElement.xpath('ns:pid', nsResolver).text(); + result.status = processElement.xpath('ns:status', nsResolver).text(); + result.version = processElement.xpath('ns:version', nsResolver).text(); + result.package = processElement.xpath('ns:deployment-info/ns:package', nsResolver).text(); + result.deployDate = processElement.xpath('ns:deployment-info/ns:deploy-date', nsResolver).text(); + + result.nameShort = processNameEl.text(); + result.nameFull = SoapService.qnameToString(SoapService.createQNameObj(processNameEl.text(), processNameEl[0])); + + // endpoints + result.endpoints = []; + processElement.xpath('ns:endpoints/ns:endpoint-ref', nsResolver).each(function() { + result.endpoints.push(vkbeautify.xml($(this).first().html().trim(), 2)); + }); + + // documents + result.documents = []; + processElement.xpath('ns:documents/ns:document', nsResolver).each(function() { + var d = {}; + d.name = $(this).xpath('ns:name', nsResolver).text(); + d.type = $(this).xpath('ns:type', nsResolver).text(); + d.source = $(this).xpath('ns:source', nsResolver).text(); + result.documents.push(d); + }); + + // properties + // TODO + + result.stats = { + active: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'ACTIVE\']/@count', nsResolver).val()), + completed: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'COMPLETED\']/@count', nsResolver).val()), + error: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'ERROR\']/@count', nsResolver).val()), + failed: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'FAILED\']/@count', nsResolver).val()), + suspended: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'SUSPENDED\']/@count', nsResolver).val()), + terminated: Number(processElement.xpath('ns:instance-summary/ns:instances[@state=\'TERMINATED\']/@count', nsResolver).val()), + inrecovery: Number(processElement.xpath('ns:instance-summary/ns:failures/ns:count', nsResolver).text() || 0) + }; + + return result; + }; + + var updateStats = function(packages) { + ps.summary = {}; + ps.summary.packages = 0; + ps.summary.processes = 0; + ps.summary.instances = 0; + + ps.summary.active = 0; + ps.summary.completed = 0; + ps.summary.suspended = 0; + ps.summary.failed = 0; + ps.summary.error = 0; + ps.summary.inrecovery = 0; + ps.summary.terminated = 0; + + packages.forEach(function(pa) { + ps.summary.packages++; + pa.processes.forEach(function(process) { + ps.summary.processes++; + + ps.summary.active += process.stats.active; + ps.summary.completed += process.stats.completed; + ps.summary.suspended += process.stats.suspended; + ps.summary.failed += process.stats.failed; + ps.summary.error += process.stats.error; + ps.summary.inrecovery += process.stats.inrecovery; + ps.summary.terminated += process.stats.terminated; + + ps.summary.instances += Object.keys(process.stats).reduce(function (a,b) { + return a + process.stats[b]; + }, 0); + + }); + }); + }; + + /** public functions **/ + var ps = {}; + + ps.getPackages = function() { + var deferred = $q.defer(); + SoapService.callSOAP(PMAPI_ENDPOINT, + {localName: 'listAllProcesses', namespaceURI: PMAPI_NS}, + {}) + .then(function(response) { + var packages = {}, + els = response.xpath('//ns:process-info', nsResolver); + + for (var i = 0; i < els.length; i += 1) { + var process = angular.element(els[i]); + var packageName = process.xpath('ns:deployment-info/ns:package', nsResolver).text(); + + packages[packageName] = packages[packageName] || _.extend(splitPackageName(packageName), + { + deployDate: process.xpath('ns:deployment-info/ns:deploy-date', nsResolver).text(), + processes: [] + }); + + packages[packageName].processes.push(mapProcessInfo(process)); + } + updateStats(_.values(packages)); + deferred.resolve(_.values(packages)); + }, function(msg, code) { + deferred.reject(msg); + $log.error(msg, code); + }); + return deferred.promise; + }; + + /** + * List the processes known to the engine. + * @returns list of {@link ProcessInfoDocument}s (including instance summaries) + */ + ps.getProcesses = function() { + var deferred = $q.defer(); + SoapService.callSOAP(PMAPI_ENDPOINT, + {localName: 'listAllProcesses', namespaceURI: PMAPI_NS}, + {}) + .then(function(response) { + var processes = [], + els = response.xpath('//ns:process-info', nsResolver), + process, + i; + + for (i = 0; i < els.length; i += 1) { + process = angular.element(els[i]); + processes.push(mapProcessInfo(process)); + } + + deferred.resolve(processes); + }, function(msg, code) { + deferred.reject(msg); + $log.error(msg, code); + }); + return deferred.promise; + }; + + /** + * Get the process info for a process (includingthe instance summary). + * @param pid name of the process + * @returns {@link ProcessInfoDocument} with all details. + */ + ps.getProcessInfo = function (pid) { + var deferred = $q.defer(); + var pidQName = SoapService.parseQNameStr(pid); + pidQName.prefix = 'pns'; + + SoapService.callSOAP(PMAPI_ENDPOINT, + {localName: 'getProcessInfo', namespaceURI: PMAPI_NS}, + {pid: pidQName}) + .then(function(response) { + deferred.resolve(mapProcessInfo(response.xpath('//process-info'))); + }, function(fault) { + deferred.reject(fault); + }); + + return deferred.promise; + }; + + /** + * Retire a process. + * @param pid identifier of the process to retire + * @param {boolean} retired retired or not? + * @return {@link ProcessInfoDocument} reflecting the modification + */ + ps.setRetired = function (pid, retired) { + var pidQName = SoapService.parseQNameStr(pid); + pidQName.prefix = 'pns'; + + return SoapService.callSOAP(PMAPI_ENDPOINT, + {localName: 'setRetired', namespaceURI: PMAPI_NS}, + {pid: pidQName, retired: retired}); + }; + + /** + * Deploy a package + * @param {string} packageName the package name. + * @param {string} zip the package zip in Base64 encoding + * @return {Promise} a promise for this request. + */ + ps.deployPackage = function (packageName, zip) { + return SoapService.callSOAP(DSAPI_ENDPOINT, + {localName: 'deploy', namespaceURI: PMAPI_NS}, + {name: packageName, + 'package': '<dep:zip xmlns:dep="http://www.apache.org/ode/deployapi">' + zip + '</dep:zip>'}); + }; + + if (POLLING_INTERVAL > 0) { + $interval(ps.getPackages, POLLING_INTERVAL); + } + + /** + * Undeploy a package + * @param {string} paid the package name. + * @return {Promise} a promise for this request. + */ + ps.undeployPackage = function (paid) { + return SoapService.callSOAP(DSAPI_ENDPOINT, + {localName: 'undeploy', namespaceURI: PMAPI_NS}, + {packageName: paid}); + }; + + if (POLLING_INTERVAL > 0) { + $interval(ps.getPackages, POLLING_INTERVAL); + } + + return ps; + +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/processactionbuttons.html ---------------------------------------------------------------------- diff --git a/src/app/process/processactionbuttons.html b/src/app/process/processactionbuttons.html new file mode 100644 index 0000000..dc892cf --- /dev/null +++ b/src/app/process/processactionbuttons.html @@ -0,0 +1,8 @@ +<div ng-controller="ProcessActionsController"> + <button type="button" class="btn btn-default btn-xs" ng-if="process.status == 'ACTIVE'" ng-click="retire(process.pid)"> + <span class="glyphicon glyphicon-pause"></span> Retire + </button> + <button type="button" class="btn btn-default btn-xs" ng-if="process.status == 'DISABLED' || process.status == 'RETIRED'" ng-click="activate(process.pid)"> + <span class="glyphicon glyphicon-fire"></span> Activate + </button> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/processactions.controller.js ---------------------------------------------------------------------- diff --git a/src/app/process/processactions.controller.js b/src/app/process/processactions.controller.js new file mode 100644 index 0000000..934f22a --- /dev/null +++ b/src/app/process/processactions.controller.js @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('odeConsole') + .controller('ProcessActionsController', function ($scope, xmlParser, $http, ProcessService, ngToast) { + + var fireProcessChangeEvent = function (action, pid) { + $scope.$emit('process-modified', {action: action, pid: pid}); + }; + + var firePackageChangeEvent = function (action, paid) { + $scope.$emit('package-modified', {action: action, paid: paid}); + }; + + $scope.retire = function(pid) { + console.log('retire ' + pid); + ProcessService.setRetired(pid, true).then(function() { + ngToast.create('Process ' + pid + ' retired.'); + fireProcessChangeEvent('retire', pid); + }, function() { + ngToast.create({content: 'Ouups, something went wrong.', class: 'danger'}); + }); + }; + + $scope.activate = function(pid) { + console.log('activate ' + pid); + ProcessService.setRetired(pid, false).then(function() { + ngToast.create('Process ' + pid + ' activated.'); + fireProcessChangeEvent('activate', pid); + }, function() { + ngToast.create({content: 'Ouups, something went wrong.', class: 'danger'}); + }); + }; + + $scope.undeployPackage = function(paid) { + console.log('undeployPackage ' + paid); + ProcessService.undeployPackage(paid).then(function() { + ngToast.create('Package ' + paid + ' undeployed.'); + firePackageChangeEvent('undeploy', paid); + }, function() { + ngToast.create({content: 'Ouups, something went wrong.', class: 'danger'}); + }); + }; + +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/processlist.controller.js ---------------------------------------------------------------------- diff --git a/src/app/process/processlist.controller.js b/src/app/process/processlist.controller.js new file mode 100644 index 0000000..db7047e --- /dev/null +++ b/src/app/process/processlist.controller.js @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +angular.module('odeConsole') + .controller('ProcessListController', function ($scope, xmlParser, $http, ProcessService, ngToast) { + + var updateTable = function () { + ProcessService.getPackages().then(function(packages) { + $scope.packages = packages; + }, function() { + ngToast.create({content: 'Could not load process list.', class: 'danger'}); + }); + }; + + $scope.upload = {}; + $scope.uploadPackage = function () { + ProcessService.deployPackage($scope.upload.packageName, $scope.upload.file.base64).then(function() { + ngToast.create({content: 'Package ' + $scope.upload.name + ' successfully deployed', class: 'success'}); + $scope.upload = {}; + updateTable(); + }, function(fault) { + ngToast.create({content: 'Could not deploy package ' + $scope.upload.file.filename + ': ' + fault.faultstring, class: 'danger'}); + $scope.upload = {}; + }); + }; + + // listen for changes issued by actions to reload table + $scope.$on('process-modified', function () { + updateTable(); + }); + $scope.$on('package-modified', function () { + updateTable(); + }); + + updateTable(); + +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/processlist.controller.spec.js ---------------------------------------------------------------------- diff --git a/src/app/process/processlist.controller.spec.js b/src/app/process/processlist.controller.spec.js new file mode 100644 index 0000000..62be2ea --- /dev/null +++ b/src/app/process/processlist.controller.spec.js @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +describe('controllers', function(){ + var scope; + + beforeEach(module('odeConsole')); + + beforeEach(inject(function($rootScope) { + scope = $rootScope.$new(); + })); + + it('should define a packages object', inject(function($controller) { + expect(scope.packages).toBeUndefined(); + + $controller('ProcessListController', { + $scope: scope + }); + + //expect(scope.packages).toBeDefined(); + })); +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/process/processlist.html ---------------------------------------------------------------------- diff --git a/src/app/process/processlist.html b/src/app/process/processlist.html new file mode 100644 index 0000000..f76704b --- /dev/null +++ b/src/app/process/processlist.html @@ -0,0 +1,79 @@ +<div class="container"> + + <h1>WS-BPEL Process Models</h1> + + <div class="panel panel-default"> + <!-- Default panel contents --> + + <table class="table table-condensed"> + <thead> + <tr> + <th></th> + <th>Process name</th> + <th>Status</th> + <th>Active</th> + <th tooltip-append-to-body="true" tooltip="Completed OK">Completed</th> + <th tooltip-append-to-body="true" tooltip="Completed with fault">Failed</th> + <th tooltip-append-to-body="true" tooltip="Suspended via a breakpoint or user intervention">Suspended</th> + <th>Terminated</th> + <th>Action</th> + </tr> + </thead> + <tbody ng-repeat="package in packages"> + <tr ng-if="!packages.length"> + <td colspan="10" ng-if="!packages.length"> + <h4 class="text-center">No instances found.</h4> + </td> + </tr> + <tr class="active"> + <td colspan="8">{{package.name}} <span class="label label-info" ng-if="package.version">Version {{package.version}}</span></td> + <td> + <button type="button" class="btn btn-danger btn-xs" ng-really-message="Are you sure?" ng-really-click="undeployPackage(package.packageId)"> + <span class="glyphicon glyphicon-remove"></span> Undeploy + </button> + </td> + </tr> + <tr ng-repeat="process in package.processes"> + <td></td> + <td data-toggle="tooltip" title="{{process.nameFull}}"><a href="#/processes/{{process.pid}}">{{process.nameShort}}</a></td> + <td><span class="label ng-class: 'label-process-' + (process.status | lowercase);">{{process.status}}</span></td> + <td>{{process.stats.active}} <span ng-if="process.stats.inrecovery > 0" class="badge" tooltip="Instances waiting for manual recovery"><i class="fa fa-exclamation-circle"></i> {{process.stats.inrecovery}}</span></td> + <td>{{process.stats.completed}}</td> + <td>{{process.stats.failed}}</td> + <td>{{process.stats.suspended}}</td> + <td>{{process.stats.terminated}}</td> + <td><div ng-include="'app/process/processactionbuttons.html'"></div></td> + </tr> + </tbody> + </table> + </div> + + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title">Upload a process model</h3> + </div> + <div class="panel-body"> + <form name="uploadForm"> + <div ng-class="{'has-error': uploadForm.$touched && !uploadForm.packageName.$valid}" class="form-group"> + <label for="packageName">Package Name</label> + <input type="text" class="form-control" id="packageName" name="packageName" ng-model="upload.packageName" placeholder="Package Name" required> + </div> + <div ng-class="{'has-error': uploadForm.touched && !uploadForm.file.$valid}"class="form-group"> + <label for="file">Process Package</label> + <div class="input-group"> + <span class="input-group-btn"> + <span class="btn btn-default btn-file"> + Browse… <input type="file" name="file" id="file" ng-model="upload.file" accept="application/zip" base-sixty-four-input required> + </span> + </span> + <input type="text" class="form-control" ng-model="upload.file.filename" readonly> + </div> + <span class="help-block"> + Select ODE deployment packages (.zip files containing all process artifacts at the root level) + </span> + </div> + <button type="submit" class="btn btn-primary" ng-click="uploadPackage()" ng-disabled="!uploadForm.$valid">Upload</button> + </div> + </form> + </div> +</div> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/really.directive.js ---------------------------------------------------------------------- diff --git a/src/app/really.directive.js b/src/app/really.directive.js new file mode 100644 index 0000000..af25bb8 --- /dev/null +++ b/src/app/really.directive.js @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +/** + * A generic confirmation for risky actions. + * Usage: Add attributes: ng-really-message="Are you sure"? ng-really-click="takeAction()" function + * Inspired from https://gist.github.com/asafge/7430497 + */ +angular.module('odeConsole').directive('ngReallyClick', ['$modal', '$parse', function($modal, $parse) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + element.bind('click', function() { + // create new child scope to pass data to dialog + var mScope = scope.$new(); + mScope.message = attrs.ngReallyMessage; + + $modal.open({ + templateUrl: 'app/confirmdialog.html', + scope: mScope + }).result.then(function() { + // destroy temporary child scope + mScope.$destroy(); + // invoke action + $parse(attrs.ngReallyClick)(scope); + }); + }); + } + }; +}]); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/soap.service.js ---------------------------------------------------------------------- diff --git a/src/app/soap.service.js b/src/app/soap.service.js new file mode 100644 index 0000000..76f4a40 --- /dev/null +++ b/src/app/soap.service.js @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use strict'; + +/** + * @ngdoc service + * @name odeConsole.SoapService + * @description + * # SoapService + * Service in the odeConsole. + */ +angular.module('odeConsole') + .factory('SoapService', function ($http, $log, $q, xmlFilter, _) { + + var soap = {}; + + /** private functions **/ + var parseSOAPFault = function (response) { + var result = {}; + var fault = xmlFilter(response.data).xpath('/*:Envelope/*:Body/*:Fault'); + if (fault) { + result.faultcode = fault.xpath('faultcode').text(); + result.faultstring = fault.xpath('faultstring').text(); + result.details = fault.xpath('details').text(); + } + + return result; + }; + + var serializeSOAPParameters = function (parameters) { + var param = ''; + for (var key in parameters) { + var value = parameters[key]; + if (_.isObject(value)) { + // then we expect a QName and serialize it accordingly + param += '<' + key + ' xmlns:' + value.prefix + '="' + value.namespaceURI + '">' + value.prefix + ':' + value.localName + '</' + key + '>'; + } else { + param += '<' + key + '>' + value + '</' + key + '>'; + } + } + return param; + }; + + var createSOAPMessage = function (operation, parameters) { + operation.prefix = operation.prefix || 'prfx'; + var message = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">' + + ' <soapenv:Header/>' + + ' <soapenv:Body>' + + ' <' + operation.prefix + ':' + operation.localName + ' xmlns:' + operation.prefix + '="' + operation.namespaceURI + '">' + + serializeSOAPParameters(parameters) + + ' </' + operation.prefix + ':' + operation.localName + '>' + + ' </soapenv:Body>' + + '</soapenv:Envelope>'; + return message; + }; + + /** public functions **/ + soap.callSOAP = function(endpoint, operation, parameters) { + var deferred = $q.defer(), + request = createSOAPMessage(operation, parameters); + + $http.post(endpoint + '/' + operation.localName, request) + .then(function(response) { + deferred.resolve(response.xml); + }, function(response) { + if (response.status !== 500) { + deferred.reject({faultcode: 'Client', faultstring: 'Network problem'}); + } else { + var fault = parseSOAPFault(response); + deferred.reject(fault); + $log.error(fault); + } + }); + + return deferred.promise; + }; + + /** Parses string representation into a QName object **/ + soap.parseQNameStr = function(qnameStr) { + if (qnameStr.charAt(0) !== '{') { + return { + namespaceURI: '', + localName: qnameStr, + prefix: '' + }; + } + + var endOfNamespaceURI = qnameStr.indexOf('}'); + return { + namespaceURI: qnameStr.substr(1, endOfNamespaceURI -1), + localName: qnameStr.substr(endOfNamespaceURI + 1), + prefix: '' + }; + }; + + soap.qnameToString = function (qname) { + return '{' + qname.namespaceURI + '}' + qname.localName; + }; + + /** Creates QName object from an XML QName **/ + soap.createQNameObj = function (qname, contextElement) { + var endOfPrefix = qname.indexOf(':'); + var prefix = qname.substr(0, endOfPrefix); + return { + namespaceURI: contextElement.lookupNamespaceURI(prefix), + localName: qname.substr(endOfPrefix + 1), + prefix: prefix + }; + }; + + return soap; +}); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/st-select.directive.js ---------------------------------------------------------------------- diff --git a/src/app/st-select.directive.js b/src/app/st-select.directive.js new file mode 100644 index 0000000..1bef969 --- /dev/null +++ b/src/app/st-select.directive.js @@ -0,0 +1,43 @@ +'use strict'; + +/** License unclear: http://jsfiddle.net/wldaunfr/JSFLa/ **/ +angular.module('odeConsole') + .directive('selectAllCheckbox', ['$filter', function($filter) { + return { + restrict: 'E', + template: '<input type="checkbox">', + replace: true, + link: function(scope, element, iAttrs) { + function changeState(checked, indet) { + element.prop('checked', checked).prop('indeterminate', indet); + } + function updateItems() { + angular.forEach(scope.$eval(iAttrs.items), function(el) { + el[iAttrs.prop] = element.prop('checked'); + }); + } + element.bind('change', function() { + scope.$apply(function() { updateItems(); }); + }); + scope.$watch(iAttrs.items, function(newValue) { + var checkedItems = $filter('filter')(newValue, function(el) { + return el[iAttrs.prop]; + }); + switch(checkedItems ? checkedItems.length : 0) { + case 0: // none selected + changeState(false, false); + break; + case newValue.length: // all selected + changeState(true, false); + break; + default: // some selected + changeState(false, true); + } + }, true); + scope.$on('$destroy', function() { + element.off('change'); + }); + updateItems(); + } + }; +}]); http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/app/vendor.scss ---------------------------------------------------------------------- diff --git a/src/app/vendor.scss b/src/app/vendor.scss new file mode 100644 index 0000000..95675a3 --- /dev/null +++ b/src/app/vendor.scss @@ -0,0 +1,71 @@ +$icon-font-path: "../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/"; + +$font-family-base: 'Roboto Slab', serif; + +@import '../../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap'; + +// custom instance labels +.label-instance-active { + @include label-variant($label-primary-bg); +} + +.label-instance-suspended { + @include label-variant($label-primary-bg); +} + +.label-instance-error { + @include label-variant($label-danger-bg); +} + +.label-instance-completed { + @include label-variant($label-success-bg); +} + +.label-instance-terminated { + @include label-variant($label-default-bg); +} + +.label-instance-faulted { + @include label-variant($label-danger-bg); +} + +.label-instance-failed { + @include label-variant($label-danger-bg); +} + +// custom instance labels +.label-process-active { + @include label-variant($label-success-bg); +} + +.label-process-retired { + @include label-variant($label-default-bg); +} + +// custom activity labels +.label-activity-started { + @include label-variant($label-primary-bg); +} + +.label-activity-enabled { + @include label-variant($label-info-bg); +} + +.label-activity-completed { + @include label-variant($label-success-bg); +} + +.label-activity-failure { + @include label-variant($label-danger-bg); +} + + +// status contextual table variants +@include table-row-variant('active', white); +@include table-row-variant('completed', $state-success-bg); +@include table-row-variant('suspended', $state-info-bg); +@include table-row-variant('terminated', $state-warning-bg); +@include table-row-variant('faulted', $state-danger-bg); +@include table-row-variant('failed', $state-danger-bg); +@include table-row-variant('error', $state-danger-bg); + http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/assets/images/ode-logo.png ---------------------------------------------------------------------- diff --git a/src/assets/images/ode-logo.png b/src/assets/images/ode-logo.png new file mode 100644 index 0000000..b99ff7b Binary files /dev/null and b/src/assets/images/ode-logo.png differ http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/components/navbar/navbar.controller.js ---------------------------------------------------------------------- diff --git a/src/components/navbar/navbar.controller.js b/src/components/navbar/navbar.controller.js new file mode 100644 index 0000000..1613e98 --- /dev/null +++ b/src/components/navbar/navbar.controller.js @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('odeConsole') + .controller('NavbarController', function ($scope, $location, ProcessService) { + $scope.isActive = function (viewLocation) { + return $location.path().indexOf(viewLocation) === 0; + }; + + // watch for summary changes and update counter badges + $scope.$watch(function() { + return ProcessService.summary; + }, function (newVal, oldVal, scope) { + scope.summary = newVal; + }); + }) +; http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/components/navbar/navbar.html ---------------------------------------------------------------------- diff --git a/src/components/navbar/navbar.html b/src/components/navbar/navbar.html new file mode 100644 index 0000000..10fa49c --- /dev/null +++ b/src/components/navbar/navbar.html @@ -0,0 +1,26 @@ +<nav class="navbar navbar-static-top navbar-default" ng-controller="NavbarController"> + <div class="container-fluid"> + <div class="navbar-header"> + <a class="navbar-brand" href="#/"> + Apache ODE<sup>â¢</sup> Console + </a> + </div> + + <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-6"> + <ul class="nav navbar-nav"> + <li ng-class="{ active: isActive('/processes')}"><a ng-href="#/processes">Process Models <span class="badge">{{summary.processes}}</span></a></li> + <li class="dropdown" dropdown ng-class="{ active: isActive('/instances')}"><a class="dropdown-toggle" dropdown-toggle role="button" aria-expanded="false">Process Instances <span class="badge">{{summary.instances}}</span><span class="caret"></span></a> + <ul class="dropdown-menu" role="menu" > + <li><a ng-href="#/instances">All Instances <span class="badge">{{summary.instances}}</span></a></li> + <li><a ng-href="#/instances?q=status%20%3D%20active" >Running Instances <span class="badge">{{summary.active}}</span></a></li> + <li><a ng-href="#/instances?q=status%20%3D%20suspended" >Suspended Instances <span class="badge">{{summary.suspended}}</span></a></li> + <li><a ng-href="#/instances?q=status%20%3D%20failed">Failed Instances <span class="badge">{{summary.failed}}</span></a></li> + <li><a ng-href="#/instances?q=status%20%3D%20completed">Completed Instances <span class="badge">{{summary.completed}}</span></a></li> + </ul> + </li> + </li> + </ul> + + </div> + </div> +</nav> http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/favicon.ico ---------------------------------------------------------------------- diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000..cac0909 Binary files /dev/null and b/src/favicon.ico differ http://git-wip-us.apache.org/repos/asf/ode-console/blob/4ee2cbad/src/index.html ---------------------------------------------------------------------- diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..269130a --- /dev/null +++ b/src/index.html @@ -0,0 +1,90 @@ +<!doctype html> +<html class="no-js" ng-app="odeConsole"> + <head> + <meta charset="utf-8"> + <title>Apache ODE Console</title> + <meta name="description" content=""> + <meta name="viewport" content="width=device-width"> + <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> + + <!-- build:css({.tmp,src}) styles/vendor.css --> + <link rel="stylesheet" href="app/vendor.css"> + <!-- bower:css --> + <link rel="stylesheet" href="../bower_components/ngtoast/dist/ngToast.css" /> + <link rel="stylesheet" href="../bower_components/font-awesome/css/font-awesome.css" /> + <link rel="stylesheet" href="../bower_components/angular-loading-bar/build/loading-bar.css" /> + <link rel="stylesheet" href="../bower_components/roboto-slab-fontface/roboto-slab-fontface.css" /> + <!-- endbower --> + <!-- endbuild --> + + + <!-- build:css({.tmp,src}) styles/app.css --> + <!-- inject:css --> + <link rel="stylesheet" href="app/index.css"> + <!-- endinject --> + <!-- endbuild --> + + <!-- build:js scripts/modernizr.js --> + <script src="../bower_components/modernizr/modernizr.js"></script> + <!-- endbuild --> + </head> + <body> + <!--[if lt IE 10]> + <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> + <![endif]--> + <toast></toast> + <div ng-include="'components/navbar/navbar.html'"></div> + + <div ng-view></div> + + <!-- build:js scripts/vendor.js --> + <!-- bower:js --> + <script src="../bower_components/jquery/dist/jquery.js"></script> + <script src="../bower_components/angular/angular.js"></script> + <script src="../bower_components/angular-animate/angular-animate.js"></script> + <script src="../bower_components/angular-sanitize/angular-sanitize.js"></script> + <script src="../bower_components/angular-route/angular-route.js"></script> + <script src="../bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> + <script src="../bower_components/angular-smart-table/dist/smart-table.min.js"></script> + <script src="../bower_components/angular-xml/angular-xml.js"></script> + <script src="../bower_components/angular-poller/angular-poller.min.js"></script> + <script src="../bower_components/moment/moment.js"></script> + <script src="../bower_components/angular-moment/angular-moment.js"></script> + <script src="../bower_components/ngtoast/dist/ngToast.js"></script> + <script src="../bower_components/underscore/underscore.js"></script> + <script src="../bower_components/angular-loading-bar/build/loading-bar.js"></script> + <script src="../bower_components/ace-builds/src-min-noconflict/ace.js"></script> + <script src="../bower_components/ace-builds/src-min-noconflict/mode-xml.js"></script> + <script src="../bower_components/angular-ui-ace/ui-ace.js"></script> + <script src="../bower_components/angular-base64-upload/src/angular-base64-upload.js"></script> + <!-- endbower --> + <!-- endbuild --> + + <!-- build:js({.tmp,src}) scripts/app.js --> + <!-- inject:js --> + <script src="app/index.js"></script> + <script src="app/really.directive.js"></script> + <script src="app/soap.service.js"></script> + <script src="app/spinbutton.directive.js"></script> + <script src="app/st-select.directive.js"></script> + <script src="vendor/jquery.xpath.js"></script> + <script src="vendor/vkbeautify.js"></script> + <script src="components/navbar/navbar.controller.js"></script> + <script src="app/dashboard/dashboard.controller.js"></script> + <script src="app/instance/instance.controller.js"></script> + <script src="app/instance/instance.service.js"></script> + <script src="app/instance/instanceactions.controller.js"></script> + <script src="app/instance/instancelist.controller.js"></script> + <script src="app/process/process.controller.js"></script> + <script src="app/process/process.service.js"></script> + <script src="app/process/processactions.controller.js"></script> + <script src="app/process/processlist.controller.js"></script> + <!-- endinject --> + + <!-- inject:partials --> + <!-- angular templates will be automaticaly converted in js and inserted here --> + <!-- endinject --> + <!-- endbuild --> + + </body> +</html>
