Integrating YANG live compilation into YANG runtime.
- Bumped ONOS dependency on ONOS YANG tools 2.2.0-b4.
- Added CLI to compile YANG models.
- Added GUI capability to compile YANG models via drag-n-drop or file upload.
- Fixed defect in propagating self-contained JAR apps through the cluster.
Change-Id: Icbd2a588bf1ffe0282e12d3d10a117e0957c3084
diff --git a/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java b/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
index 8bf6876..b048744 100644
--- a/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
+++ b/apps/yang-gui/src/main/java/org/onosproject/yang/gui/YangModelMessageHandler.java
@@ -161,7 +161,7 @@
// FIXME: Hack to properly resolve the YANG source resource
private InputStream getSource(String modelId, YangModule module) {
try {
- module.getYangSource(); // trigger exception
+ return module.getYangSource(); // trigger exception
} catch (ModelException e) {
// Strip the YANG source file base-name and then use it to access
// the corresponding resource in the correct run-time context.
@@ -172,7 +172,6 @@
return loader == null ? null :
loader.getResourceAsStream("/yang/resources" + baseName);
}
- return null;
}
private void addSource(ArrayNode source, InputStream yangSource) {
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
index c26ade5..971e133 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel-theme.css
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+/* -- Drag-n-Drop YANG/ZIP files -- */
+div.dropping {
+ border: solid 3px #0095d6;
+}
+
.light #yang-model-details-panel .src-frame {
background-color: #f4f4f4;
}
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
index bf3dce6..f65e636 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
@@ -14,11 +14,23 @@
* limitations under the License.
*/
-
#ov-yang-model h2 {
display: inline-block;
}
+#ov-yang-model div.ctrl-btns {
+ width: 250px;
+}
+
+/* -- Drag-n-Drop file upload -- */
+#ov-yang-model form#inputYangFileForm,
+#ov-yang-model input#uploadYangFile {
+ display: none;
+}
+
+.dropping {
+
+}
#yang-model-details-panel.floatpanel {
z-index: 0;
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
index b49bcd8..6cbb0e1 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.html
@@ -1,5 +1,5 @@
<!-- YANG Model partial HTML -->
-<div id="ov-yang-model">
+<div id="ov-yang-model" yangfiledrop on-file-drop="yangDropped()">
<div class="tabular-header">
<h2>YANG Models ({{tableData.length}} total)</h2>
@@ -8,6 +8,19 @@
icon icon-id="refresh" icon-size="42"
tooltip tt-msg="autoRefreshTip"
ng-click="toggleRefresh()"></div>
+
+ <div class="separator"></div>
+
+ <form id="inputYangFileForm">
+ <input id="uploadYangFile"
+ type="file" size="50" accept=".zip, *.jar, *.yang"
+ yang-file-model="yangFile">
+ </form>
+
+ <div icon icon-size="42" icon-id="nav_yang"
+ class="active" trigger-yang-form
+ tooltip tt-msg="uploadTip">
+ </div>
</div>
</div>
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
index 00b6643..42fa343 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
@@ -44,7 +44,8 @@
// constants
var pName = 'yang-model-details-panel',
detailsReq = 'yangModelDetailsRequest',
- detailsResp = 'yangModelDetailsResponse';
+ detailsResp = 'yangModelDetailsResponse',
+ fileUploadUrl = '/onos/yang/models?modelId=';
function createDetailsPanel() {
@@ -135,11 +136,12 @@
// defines view controller
angular.module('ovYangModel', [])
.controller('OvYangModelCtrl', [
- '$log', '$scope', 'TableBuilderService', 'TableDetailService',
+ '$log', '$scope', '$http', '$timeout',
+ 'TableBuilderService', 'TableDetailService',
'FnService', 'MastService', 'PanelService', 'WebSocketService',
- 'IconService',
+ 'IconService', 'UrlFnService', 'FlashService',
- function (_$log_, _$scope_, tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_) {
+ function (_$log_, _$scope_, $http, $timeout, tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, ufs, _flash_) {
var handlers = {};
$log = _$log_;
@@ -176,6 +178,45 @@
selCb: selCb
});
+ $scope.$on('YangFileChanged', function () {
+ var formData = new FormData(),
+ url, modelId, finished = false;
+
+ if ($scope.yangFile) {
+ modelId = $scope.yangFile.name;
+ modelId = modelId.substr(0, modelId.lastIndexOf("."));
+ url = fileUploadUrl + modelId;
+ formData.append('file', $scope.yangFile);
+ $log.info('Compiling', $scope.yangFile);
+ d3.select('#frame').classed('dropping', false);
+
+ // FIXME: Replace this with dialog that shows progress...
+ for (var i = 0; i < 10; i++) {
+ $timeout(function () {
+ if (!finished) _flash_.flash('Compiling ' + modelId);
+ }, i * 1100);
+ }
+
+ $http.post(url, formData, {
+ transformRequest: angular.identity,
+ headers: {
+ 'Content-Type': undefined
+ }
+ })
+ .finally(function () {
+ finished = true;
+ _flash_.flash('Compile completed for ' + modelId);
+ $scope.sortCallback($scope.sortParams);
+ document.getElementById('inputYangFileForm').reset();
+ });
+ }
+ });
+
+ $scope.yangDropped = function() {
+ $scope.$emit('YangFileChanged');
+ $scope.yangFile = null;
+ };
+
$scope.$on('$destroy', function () {
wss.unbindHandlers(handlers);
});
@@ -184,6 +225,78 @@
}
])
+ // triggers the input form to appear when button is clicked
+ .directive('triggerYangForm', function () {
+ return {
+ restrict: 'A',
+ link: function (scope, elem) {
+ elem.bind('click', function () {
+ document.getElementById('uploadYangFile')
+ .dispatchEvent(new MouseEvent('click'));
+ });
+ }
+ };
+ })
+
+ // binds the model file to the scope in scope.yangFile
+ // sends upload request to the server
+ .directive('yangFileModel', ['$parse',
+ function ($parse) {
+ return {
+ restrict: 'A',
+ link: function (scope, elem, attrs) {
+ var model = $parse(attrs.yangFileModel),
+ modelSetter = model.assign;
+
+ elem.bind('change', function () {
+ scope.$apply(function () {
+ modelSetter(scope, elem[0].files[0]);
+ });
+ scope.$emit('YangFileChanged');
+ });
+ }
+ };
+ }
+ ])
+
+ .directive("yangfiledrop", ['$parse', '$document', function ($parse, $document) {
+ return {
+ restrict: "A",
+ link: function (scope, element, attrs) {
+ var onYangDrop = $parse(attrs.onFileDrop);
+
+ // When an item is dragged over the document
+ var onDragOver = function (e) {
+ d3.select('#frame').classed('dropping', true);
+ e.preventDefault();
+ };
+
+ // When the user leaves the window, cancels the drag or drops the item
+ var onDragEnd = function (e) {
+ d3.select('#frame').classed('dropping', false);
+ e.preventDefault();
+ };
+
+ // When a file is dropped
+ var loadFile = function (file) {
+ scope.yangFile = file;
+ scope.$apply(onYangDrop(scope));
+ };
+
+ // Dragging begins on the document
+ $document.bind("dragover", onDragOver);
+
+ // Dragging ends on the overlay, which takes the whole window
+ element.bind("dragleave", onDragEnd)
+ .bind("drop", function (e) {
+ $log.info('Drag leave', e);
+ loadFile(e.dataTransfer.files[0]);
+ onDragEnd(e);
+ });
+ }
+ };
+ }])
+
.directive('yangModelDetailsPanel', [
'$rootScope', '$window', '$timeout', 'KeyService',