strolch-wc-inspector/strolch-wc-control.html

784 lines
30 KiB
HTML

<!-- Imports -->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-ajax/iron-ajax.html">
<link rel="import" href="../paper-dialog/paper-dialog.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="../paper-listbox/paper-listbox.html">
<link rel="import" href="../paper-card/paper-card.html">
<link rel="import" href="../paper-ripple/paper-ripple.html">
<link rel="import" href="../strolch-wc-styles/strolch-wc-styles.html">
<link rel="import" href="../strolch-wc-tree/strolch-wc-tree.html">
<link rel="import" href="../strolch-wc-ws-observer/strolch-wc-ws-observer.html">
<dom-module id="strolch-wc-control">
<template>
<!-- Style -->
<style is="custom-style" include="strolch-wc-styles">
:host {
--header-height: 0px;
@apply(--strolch-wc-control);
@apply(--layout-vertical);
}
.toolbar {
margin: 10px;
}
.content {
@apply(--layout-horizontal);
@apply(--layout-start-aligned);
}
paper-card {
width: 50%;
margin: 16px;
padding: 16px;
}
paper-button {
background-color: white;
}
.label {
font-weight: bolder;
width: 100px;
}
.item-btns {
margin-top: 50px;
display: flex;
justify-content: flex-end;
}
.icon-img {
margin-bottom: -4px;
}
#modelDlg {
width: 600px;
}
textarea {
padding: 10px;
margin: 10px;
width: calc(100% - 40px);
height: 100%;
min-height: 300px;
}
.action-btns {
display: flex;
}
.tree-view, .tree-item {
overflow: auto;
height: calc(100vh - 115px - var(--header-height));
}
</style>
<!-- Requests -->
<iron-ajax id="ajax"
content-type="application/json"
handle-as="json"
on-response="_handleAjaxResponse"
on-error="onAjaxError"></iron-ajax>
<iron-ajax id="ajaxGetActivities"
content-type="application/json"
handle-as="json"
method="GET"
on-response="onGetActivitiesResponse"
on-error="onAjaxError"></iron-ajax>
<iron-ajax id="ajaxSetState"
content-type="application/json"
handle-as="json"
method="PUT"
on-response="onPutActivitiesResponse"
on-error="onAjaxError"></iron-ajax>
<strolch-wc-ws-observer id="observerHandler" ws-path="[[baseWsPath]]/strolch/observer"></strolch-wc-ws-observer>
<!-- Content -->
<div class="toolbar">
<paper-dropdown-menu always-float-label label="Realm" class="custom">
<paper-listbox attr-for-selected="value" class="dropdown-content" selected="{{selectedRealm}}">
<template is="dom-repeat" items="{{realms}}">
<paper-item value="[[item.name]]">[[item.name]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
<div class="g-pull-right action-btns">
<paper-input no-label-float label="<Type>/<ID>" value="{{newLocator}}"></paper-input>
<paper-button raised on-tap="onAddActivity" title="Adds the given activitiy to the execution handler">
Add
</paper-button>
<paper-button raised on-tap="onTrigger" title="Triggers the execution handler to execute actions">
Trigger
</paper-button>
<paper-button raised on-tap="onRemoveAll" title="Removes all activities from the execution handler">
Remove All
</paper-button>
<paper-button raised
on-tap="onReload"
title="Adds all activities in execution to the execution handler">Reload
</paper-button>
<template is="dom-if" if="[[isRunning(state)]]">
<paper-button raised on-tap="onHaltNew" title="Halts the execution of new activities">Halt New
</paper-button>
<paper-button raised
on-tap="onPause"
title="Pauses the execution handler so no actions are executed">Pause
</paper-button>
</template>
<template is="dom-if" if="[[isHaltNew(state)]]">
<paper-button raised
on-tap="onPause"
title="Pauses the execution handler so no actions are executed">Pause
</paper-button>
<paper-button raised on-tap="onRun" title="Set the execution handler to normal execution">Run
</paper-button>
</template>
<template is="dom-if" if="[[isPaused(state)]]">
<paper-button raised on-tap="onHaltNew" title="Halts the execution of new activities">Halt New
</paper-button>
<paper-button raised on-tap="onRun" title="Set the execution handler to normal execution">Run
</paper-button>
</template>
<paper-icon-button icon="refresh"
title="Refresh"
on-tap="reload"
title="Refresh the view"></paper-icon-button>
</div>
</div>
<template is="dom-if" if="[[empty(elements)]]">
<p class="g-center"><i>No activities currently available</i></p>
</template>
<template is="dom-if" if="[[!empty(elements)]]">
<div class="content">
<paper-card class="tree-view">
<strolch-wc-tree title-html func-obj="{{funcObj}}" elements="[[elements]]" />
</paper-card>
<paper-card class="tree-item">
<template is="dom-if" if="[[!selectedItem]]">
<p class="g-center"><i>Select an item in the tree view</i></p>
</template>
<template is="dom-if" if="[[selectedItem]]">
<paper-button class="g-pull-right" raised on-tap="onShowModel">JSON</paper-button>
<h3>[[selectedItem.objectType]]</h3>
<table class="g-table g-table-no-border">
<tr>
<td class="label">Id:</td>
<td>[[selectedItem.id]]</td>
</tr>
<tr>
<td class="label">Name:</td>
<td>[[selectedItem.name]]</td>
</tr>
<tr>
<td class="label">Type:</td>
<td>[[selectedItem.type]]</td>
</tr>
<tr>
<td class="label">State:</td>
<td><img height="16" width="16" class="icon-img" src$="{{_getIcon(selectedItem)}}" />
[[selectedItem.state]]
</td>
</tr>
<tr>
<td class="label">Start:</td>
<td>[[formatDateTime(selectedItem.start)]]</td>
</tr>
<tr>
<td class="label">End:</td>
<td>[[formatDateTime(selectedItem.end)]]</td>
</tr>
<tr>
<td class="label">Duration:</td>
<td>[[calcDuration(selectedItem)]]</td>
</tr>
<template is="dom-if" if="[[isActivity(selectedItem)]]">
<tr>
<td class="label">TimeOrdering:</td>
<td>[[selectedItem.timeOrdering]]</td>
</tr>
</template>
<template is="dom-if" if="[[isRootElement(selectedItem)]]">
<tr>
<td class="label">Order:</td>
<td>
Order/[[selectedItem.parameterBags.relations.parameters.order.uom]]/[[selectedItem.parameterBags.relations.parameters.order.value]]
</td>
</tr>
<tr>
<td class="label">Username:</td>
<td>[[getUsername(selectedItem)]]</td>
</tr>
</template>
<template is="dom-if" if="[[isAction(selectedItem)]]">
<tr>
<td class="label">Resource:</td>
<td>Resource/[[selectedItem.resourceType]]/[[selectedItem.resourceId]]</td>
</tr>
<tr>
<td class="label">Policy:</td>
<td>[[selectedItem.executionPolicy]]</td>
</tr>
</template>
<tr>
<td class="label">Locator:</td>
<td style="overflow: auto;">[[selectedItem.locator]]</td>
</tr>
</table>
<div class="item-btns">
<template is="dom-if" if="[[canExecute(selectedItem)]]">
<paper-button raised on-tap="onToExecution">Execute</paper-button>
</template>
<template is="dom-if" if="[[isRootElement(selectedItem)]]">
<paper-button raised on-tap="onRemove">Remove</paper-button>
</template>
<template is="dom-if" if="[[isAction(selectedItem)]]">
<paper-button raised on-tap="onToWarning">Warning</paper-button>
<paper-button raised on-tap="onToError">Error</paper-button>
<paper-button raised on-tap="onToStopped">Stop</paper-button>
<paper-button raised on-tap="onToExecuted">Executed</paper-button>
</template>
</div>
</template>
</paper-card>
</div>
</template>
<paper-dialog id="modelDlg" modal>
<h3>[[selectedItem.objectType]]</h3>
<h4>[[selectedItem.name]]</h4>
<textarea value="{{modelAsText::input}}"></textarea>
<div class="buttons">
<paper-button dialog-confirm autofocus>Close</paper-button>
</div>
</paper-dialog>
</template>
<script>
Polymer({
/* Component */
is: "strolch-wc-control",
behaviors: [
StrolchInspectorBehavior
],
/* Properties */
properties: {
selectedRealm: {
type: Object,
value: function () {
return null;
},
observer: 'selectedRealmChanged'
},
selectedItem: {
type: Object,
value: null
},
newLocator: {
type: String,
value: ''
},
modelAsText: {
type: String,
value: ''
},
elements: {
type: Array,
value: null
},
funcObj: {
type: Object,
value: null
},
state: {
type: String,
value: ""
},
registeredForUpdates: {
type: Boolean,
value: false
},
},
/* Computations */
formatDateTime: function (string) {
return Strolch.toLocalDateTime(string);
},
calcDuration: function (element) {
if (element == null) return "-";
if (element.start == '-' || element.end == '-') return "-";
var a = new Date(element.start);
var b = new Date(element.end);
return this.formatMsDuration(b.getTime() - a.getTime());
},
formatMsDuration: function (millis) {
if (millis >= 3600000) {
return (Math.ceil((millis / 3600000.0) * 10) / 10) + "h";
} else if (millis >= 60000) {
return (Math.ceil((millis / 60000.0) * 10) / 10) + "m";
} else if (millis >= 1000) {
return (Math.ceil((millis / 1000.0) * 10) / 10) + "s";
} else if (millis == 0) {
return "0s";
} else {
return millis + "ms";
}
},
empty: function (arr) {
return arr == null || arr.length == 0;
},
isActivity: function (item) {
return item != null && item.objectType == 'Activity';
},
isAction: function (item) {
return item != null && item.objectType == 'Action';
},
isRootElement: function (item) {
return item != null && this.elements.find(function (e) {
return e.id === item.id
}) != null;
},
getUsername: function (item) {
if (item == null || item.parameterBags == null || item.parameterBags.parameters == null || item.parameterBags.parameters.parameters == null)
return "unknown";
var params = item.parameterBags.parameters.parameters;
if (params.username)
return item.parameterBags.parameters.parameters.username.value;
else if (params.userName)
return item.parameterBags.parameters.parameters.userName.value;
else if (item.version)
return item.version.createdBy + " / " + item.version.updatedBy;
else
return "unknown";
},
canExecute: function (item) {
return item != null && (item.objectType == 'Action' || this.isRootElement(item));
},
isRunning: function (state) {
return state === 'Running';
},
isHaltNew: function (state) {
return state === 'HaltNew';
},
isPaused: function (state) {
return state === 'Paused';
},
/* Observers */
observers: [],
selectedRealmChanged: function (newValue, oldValue) {
if (newValue != null) {
Strolch.setQueryParamValue('realm', newValue);
this.reloadControllers();
}
},
onShowModel: function (e) {
this.modelAsText = JSON.stringify(this.selectedItem, null, 2);
this.$.modelDlg.open();
},
/*
* Activity actions
*/
onToExecution: function (e) {
if (this.selectedItem != null)
this._setState('Execution');
},
onToWarning: function (e) {
if (this.selectedItem != null)
this._setState('Warning');
},
onToError: function (e) {
if (this.selectedItem != null)
this._setState('Error');
},
onToStopped: function (e) {
if (this.selectedItem != null)
this._setState('Stopped');
},
onToExecuted: function (e) {
if (this.selectedItem != null)
this._setState('Executed');
},
_setState: function (state) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/activity/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
locator: this.selectedItem.locator,
state: state
};
this.$.ajaxSetState.generateRequest();
},
onRemove: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/activity/state';
this.$.ajaxSetState.method = 'DELETE';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
type: this.selectedItem.type,
id: this.selectedItem.id
};
this.$.ajaxSetState.generateRequest();
},
/**
* Execution Handler
*/
onTrigger: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/executionHandler/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
state: 'Trigger'
};
this.$.ajaxSetState.generateRequest();
},
onHaltNew: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/executionHandler/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
state: 'HaltNew'
};
this.$.ajaxSetState.generateRequest();
},
onRun: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/executionHandler/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
state: 'Running'
};
this.$.ajaxSetState.generateRequest();
},
onPause: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/executionHandler/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
state: 'Paused'
};
this.$.ajaxSetState.generateRequest();
},
onReload: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/executionHandler/state';
this.$.ajaxSetState.method = 'PUT';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
state: 'ReloadActivities'
};
this.$.ajaxSetState.generateRequest();
},
onAddActivity: function (e) {
if (this.newLocator == null || this.newLocator.length == 0) {
this.fire('strolch-show-dialog', {
title: 'Error',
text: 'Please enter a locator'
});
return;
}
var locatorParts = this.newLocator.split("/");
if (locatorParts.length !== 2) {
this.fire('strolch-show-dialog', {
title: 'Error',
text: 'Please enter a valid locator: <Activity>/<type>/<id>'
});
return;
}
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/activity/state';
this.$.ajaxSetState.method = 'POST';
this.$.ajaxSetState.params = {
realm: this.selectedRealm,
type: locatorParts[0],
id: locatorParts[1],
state: 'Execution'
};
this.$.ajaxSetState.generateRequest();
},
onRemoveAll: function (e) {
this.$.ajaxSetState.url = this.basePath + 'rest/strolch/control/all';
this.$.ajaxSetState.method = 'DELETE';
this.$.ajaxSetState.params = {
realm: this.selectedRealm
};
this.$.ajaxSetState.generateRequest();
},
reloadRealms: function () {
var realmToSelect = Strolch.getQueryParamValue('realm');
this._handleAjaxResponse = function (data) {
this.selectedRealm = null;
this.realms = data.detail.response.realms;
if (Strolch.isNotEmptyString(realmToSelect)) {
for (var i = 0; i < this.realms.length; i++) {
if (this.realms[i].name == realmToSelect) {
this.selectedRealm = realmToSelect;
break;
}
}
}
if (this.selectedRealm == null && this.realms.length > 0) {
this.selectedRealm = this.realms[0].name;
}
};
this.$.ajax.url = this.basePath + 'rest/strolch/inspector';
this.$.ajax.method = 'GET';
this.$.ajax.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
this.$.ajax.generateRequest();
},
reloadControllers: function (e) {
clearTimeout(this.reloadTimeout);
if (this.selectedRealm != null) {
this.$.ajaxGetActivities.url = this.basePath + 'rest/strolch/control';
this.$.ajaxGetActivities.params = {realm: this.selectedRealm};
this.$.ajaxGetActivities.generateRequest();
}
},
onGetActivitiesResponse: function (e) {
this.state = e.detail.response.state;
var elements = e.detail.response.data;
this._updateSelectedItem(elements);
this.elements = elements;
},
_updateSelectedItem: function (elements) {
if (this.selectedItem == null)
return;
var elementToSelect = null;
for (var i = 0; i < elements.length; i++) {
var rootElement = elements[i];
if (this.selectedItem.locator.startsWith(rootElement.locator)) {
elementToSelect = this._getSubElementToSelect(rootElement);
break;
}
}
if (elementToSelect != null) {
this.selectedItem = elementToSelect;
} else {
this.selectedItem = null;
}
},
_getSubElementToSelect: function (rootElement) {
var parts = this.selectedItem.locator.split('/');
if (parts.length === 3) {
return rootElement;
}
var depth = 3;
var element = rootElement;
while (depth < parts.length && element.elements != null && element.elements.length > 0) {
for (var j = 0; j < element.elements.length; j++) {
if (element.elements[j].id === parts[depth]) {
element = element.elements[j];
depth++;
break;
}
}
if (element.locator === this.selectedItem.locator) {
return element;
}
}
return null;
},
onPutActivitiesResponse: function (e) {
this.newLocator = null;
//this.reloadControllers();
},
/* Listeners */
listeners: {
'strolch-wc-tree-item-selected': 'onItemSelected'
},
onItemSelected: function (e) {
this.selectedItem = e.detail.item;
},
/* Private */
_getIcon: function (item) {
if (item == null || item.state == null || item.state === '') return '';
return this.resolveUrl('icons/' + item.state + '.png');
},
/* Public */
/* Lifecycle */
ready: function () {
this.funcObj = {
getTitle: function (item) {
return item == null ? "" : "<b>" + item.type + "</b>:&#9;&#9;" + item.name;
}.bind(this),
useIcon: function (item) {
return item != null && item.state != null && item.state !== '';
},
getIcon: function (item) {
if (item == null || item.state == null || item.state === '') return '';
return this.resolveUrl('icons/' + item.state + '.png');
}.bind(this),
hasElements: function (item) {
return item != null && item.elements && item.elements.length > 0;
}.bind(this),
getElements: function (item) {
return item == null ? [] : (item.elements ? item.elements : []);
}.bind(this)
};
},
reload: function () {
this.reloadRealms();
if (!this.registeredForUpdates) {
this.$.observerHandler.register("Controller", "*", "strolch-wc-control", {
flat: false,
withLocator: true,
withVersion: true
}, this.handleUpdate.bind(this));
this.registeredForUpdates = true;
}
},
handleUpdate: function (notifyType, objectType, type, elements) {
console.log("Received " + notifyType + " updates for " + elements.length + " " + objectType + " " + type + " elements.");
switch (notifyType) {
case "ObserverAdd" : {
if (this.elements == null || this.elements.length === 0) {
this.elements = elements;
} else {
for (var i = 0; i < elements.length; i++) {
this.push("elements", elements[i]);
}
}
break;
}
case "ObserverUpdate" : {
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
for (var j = 0; j < this.elements.length; j++) {
if (this.elements[j].id === element.id) {
console.log("Updating activity: " + element.locator);
this.set('elements.' + j, element);
if (this.selectedItem != null && this.selectedItem.locator.startsWith(element.locator)) {
console.log("SelectedItem update: " + this.selectedItem.locator);
this.selectedItem = this._getSubElementToSelect(element);
}
break;
}
}
}
break;
}
case "ObserverRemove" : {
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
for (var j = 0; j < this.elements.length; j++) {
if (this.elements[j].id === element.id) {
console.log("Remove activity: " + element.locator);
this.splice('elements', j, 1);
if (this.selectedItem != null && this.selectedItem.locator.startsWith(element.locator)) {
console.log("SelectedItem remove: " + this.selectedItem.locator);
this.selectedItem = null;
}
break;
}
}
}
if (this.elements.length === 0) {
this.elements = [];
}
break;
}
default:
console.error("Received unhandled update for " + elements.length + " " + objectType + " " + type + " elements.");
}
}
});
</script>
</dom-module>