You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by sh...@apache.org on 2018/07/26 10:58:25 UTC
[kylin] branch master updated: KYLIN-3418 User interface for hybrid
model - Frontend
This is an automated email from the ASF dual-hosted git repository.
shaofengshi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git
The following commit(s) were added to refs/heads/master by this push:
new f5e28fb KYLIN-3418 User interface for hybrid model - Frontend
f5e28fb is described below
commit f5e28fb60ef42551deb58e81b08499e725afdf74
Author: Emiya0306 <wo...@qq.com>
AuthorDate: Thu Jul 26 09:59:18 2018 +0800
KYLIN-3418 User interface for hybrid model - Frontend
---
webapp/app/fonts/kylin.eot | Bin 0 -> 1600 bytes
webapp/app/fonts/kylin.svg | 13 +
webapp/app/fonts/kylin.ttf | Bin 0 -> 1436 bytes
webapp/app/fonts/kylin.woff | Bin 0 -> 1512 bytes
webapp/app/image/checkbox+.svg | 15 +
webapp/app/image/checkbox-.svg | 17 +
webapp/app/index.html | 22 ++
webapp/app/js/controllers/hybridInstance.js | 110 ++++++
webapp/app/js/controllers/hybridInstanceSchema.js | 404 ++++++++++++++++++++++
webapp/app/js/directives/directives.js | 4 +-
webapp/app/js/model/hybridInstanceManager.js | 60 ++++
webapp/app/js/services/hybridInstance.js | 28 ++
webapp/app/less/app.less | 106 ++++++
webapp/app/less/build.less | 1 +
webapp/app/less/font.less | 36 ++
webapp/app/partials/cubes/hybrid_edit.html | 176 ++++++++++
webapp/app/partials/directives/pagination.html | 2 +-
webapp/app/partials/models/models_tree.html | 113 +++---
webapp/app/routes.json | 14 +
19 files changed, 1076 insertions(+), 45 deletions(-)
diff --git a/webapp/app/fonts/kylin.eot b/webapp/app/fonts/kylin.eot
new file mode 100755
index 0000000..4cb39d1
Binary files /dev/null and b/webapp/app/fonts/kylin.eot differ
diff --git a/webapp/app/fonts/kylin.svg b/webapp/app/fonts/kylin.svg
new file mode 100755
index 0000000..df507bb
--- /dev/null
+++ b/webapp/app/fonts/kylin.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>Generated by IcoMoon</metadata>
+<defs>
+<font id="icomoon" horiz-adv-x="1024">
+<font-face units-per-em="1024" ascent="960" descent="-64" />
+<missing-glyph horiz-adv-x="1024" />
+<glyph unicode=" " horiz-adv-x="512" d="" />
+<glyph unicode="" glyph-name="hybrid" d="M538.389 557.895h282.93v-140.317l199.184-107.26 2.914-2.914 0.925-2.867v-232.435c0-3.237-1.295-5.457-3.838-6.752l-200.617-108.032h-0.971c-0.647-0.647-1.619-0.971-2.914-0.971s-2.266 0.324-2.914 0.971h-0.925l-200.617 108.032c-2.59 1.295-3.885 3.515-3.885 6.752v232.435c0 0.601 0.324 1.295 0.971 1.942v0.971l2.914 2.914 156.668 84.329v110.095l-541.185 0.276v-94.685l185.815-100.061 2.914-2.914 0.925-2.867v-232.435c0-3.237-1.295-5.457-3.838-6.752 [...]
+<glyph unicode="" glyph-name="arrows_right" d="M761.077 449.819l-456.626 392.050c-16.091 13.815-17.935 38.059-4.12 54.149s38.059 17.935 54.149 4.12l490.56-421.184c17.847-15.323 17.847-42.946 0-58.27l-490.56-421.184c-16.091-13.815-40.334-11.97-54.149 4.12s-11.97 40.334 4.12 54.149l456.626 392.050z" />
+<glyph unicode="" glyph-name="arrows_left" d="M242.824 449.819l456.626-392.050c16.091-13.815 17.935-38.059 4.12-54.149s-38.059-17.935-54.149-4.12l-490.56 421.184c-17.847 15.323-17.847 42.946 0 58.27l490.56 421.184c16.091 13.815 40.334 11.97 54.149-4.12s11.97-40.334-4.12-54.149l-456.626-392.050z" />
+</font></defs></svg>
\ No newline at end of file
diff --git a/webapp/app/fonts/kylin.ttf b/webapp/app/fonts/kylin.ttf
new file mode 100755
index 0000000..8294ac9
Binary files /dev/null and b/webapp/app/fonts/kylin.ttf differ
diff --git a/webapp/app/fonts/kylin.woff b/webapp/app/fonts/kylin.woff
new file mode 100755
index 0000000..4b6f197
Binary files /dev/null and b/webapp/app/fonts/kylin.woff differ
diff --git a/webapp/app/image/checkbox+.svg b/webapp/app/image/checkbox+.svg
new file mode 100644
index 0000000..b8630a3
--- /dev/null
+++ b/webapp/app/image/checkbox+.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
+ <title>Group Copy 2</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Hybrid-add-cubes" transform="translate(-34.000000, -1331.000000)">
+ <g id="Group-Copy-2" transform="translate(34.000000, 1331.000000)">
+ <rect id="bg" fill="#0988DE" x="0" y="0" width="16" height="16" rx="2"></rect>
+ <path d="M4.51040764,8.01040764 L13.5104076,8.01040764 L13.5104076,10.0104076 L2.51040764,10.0104076 L2.51040764,8.01040764 L2.51040764,4.01040764 L4.51040764,4.01040764 L4.51040764,8.01040764 Z" id="check" fill="#FFFFFF" transform="translate(8.010408, 7.010408) rotate(-45.000000) translate(-8.010408, -7.010408) "></path>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/webapp/app/image/checkbox-.svg b/webapp/app/image/checkbox-.svg
new file mode 100644
index 0000000..6de232a
--- /dev/null
+++ b/webapp/app/image/checkbox-.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
+ <title>bg copy 2</title>
+ <desc>Created with Sketch.</desc>
+ <defs>
+ <rect id="path-1" x="34" y="395" width="16" height="16" rx="2"></rect>
+ </defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Hybrid-add-cubes" transform="translate(-34.000000, -395.000000)">
+ <g id="bg-copy-2">
+ <use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
+ <rect stroke="#CFD8DC" stroke-width="1" x="34.5" y="395.5" width="15" height="15" rx="2"></rect>
+ </g>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/webapp/app/index.html b/webapp/app/index.html
index 12daaa2..8f7e954 100644
--- a/webapp/app/index.html
+++ b/webapp/app/index.html
@@ -149,6 +149,7 @@
<script src="js/services/acl.js"></script>
<!--New GUI-->
<script src="js/services/models.js"></script>
+<script src="js/services/hybridInstance.js"></script>
<script src="js/services/dashboard.js"></script>
<script src="js/model/cubeConfig.js"></script>
@@ -172,6 +173,7 @@
<!--New GUI-->
<script src="js/model/modelsManager.js"></script>
+<script src="js/model/hybridInstanceManager.js"></script>
<script src="js/services/badQuery.js"></script>
<script src="js/utils/utils.js"></script>
<script src="js/controllers/page.js"></script>
@@ -210,6 +212,8 @@
<!--New GUI-->
<script src="js/controllers/models.js"></script>
+<script src="js/controllers/hybridInstanceSchema.js"></script>
+<script src="js/controllers/hybridInstance.js"></script>
<script src="js/controllers/dashboard.js"></script>
<!-- endref -->
@@ -256,6 +260,24 @@
</div>
</script>
+<!-- static template for hybrid cube save/update result notification -->
+<script type="text/ng-template" id="hybridResultError.html">
+ <div class="callout callout-info">
+ <h4>Error Message</h4>
+ <p>{{text}}</p>
+ </div>
+ <div class="callout callout-danger">
+ <h4>Hybrid Instance Schema</h4>
+ <pre>{{schema}}</pre>
+ </div>
+</script>
+
+<script type="text/ng-template" id="hybridResultSuccess.html">
+ <div class="callout callout-info">
+ <p>{{text}}</p>
+ </div>
+</script>
+
<!-- static template for cube save/update result notification -->
<script type="text/ng-template" id="streamingResultError.html">
<div class="callout">
diff --git a/webapp/app/js/controllers/hybridInstance.js b/webapp/app/js/controllers/hybridInstance.js
new file mode 100644
index 0000000..152feab
--- /dev/null
+++ b/webapp/app/js/controllers/hybridInstance.js
@@ -0,0 +1,110 @@
+/*
+ * 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';
+
+KylinApp.controller('HybridInstanceCtrl', function (
+ $scope, $q, $location,
+ ProjectModel, hybridInstanceManager, SweetAlert, HybridInstanceService, loadingRequest
+) {
+ $scope.projectModel = ProjectModel;
+ $scope.hybridInstanceManager = hybridInstanceManager;
+
+ //trigger init with directive []
+ $scope.list = function () {
+ var defer = $q.defer();
+ var queryParam = {};
+ if (!$scope.projectModel.isSelectedProjectValid()) {
+ defer.resolve([]);
+ return defer.promise;
+ }
+
+ if (!$scope.projectModel.projects.length) {
+ defer.resolve([]);
+ return defer.promise;
+ }
+ queryParam.project = $scope.projectModel.selectedProject;
+ return hybridInstanceManager.list(queryParam).then(function (resp) {
+ defer.resolve(resp);
+ hybridInstanceManager.loading = false;
+ return defer.promise;
+ });
+ };
+
+ $scope.list();
+
+ $scope.$watch('projectModel.selectedProject', function() {
+ $scope.list();
+ });
+
+ $scope.editHybridInstance = function(hybridInstance){
+ // check for empty project of header, break the operation.
+ if (ProjectModel.selectedProject === null) {
+ SweetAlert.swal('Oops...', 'Please select your project first.', 'warning');
+ $location.path("/models");
+ return;
+ }
+
+ $location.path("/hybrid/edit/" + hybridInstance.name);
+ };
+
+ $scope.dropHybridInstance = function (hybridInstance) {
+
+ // check for empty project of header, break the operation.
+ if (ProjectModel.selectedProject === null) {
+ SweetAlert.swal('Oops...', 'Please select your project first.', 'warning');
+ $location.path("/models");
+ return;
+ }
+
+ SweetAlert.swal({
+ title: '',
+ text: 'Are you sure to drop this hybrid?',
+ type: '',
+ showCancelButton: true,
+ confirmButtonColor: '#DD6B55',
+ confirmButtonText: "Yes",
+ closeOnConfirm: true
+ }, function (isConfirm) {
+ if (isConfirm) {
+ var schema = {
+ hybrid: hybridInstance.name,
+ model: hybridInstance.model,
+ project: hybridInstance.project,
+ };
+
+ loadingRequest.show();
+ HybridInstanceService.drop(schema, {}, function (result) {
+ loadingRequest.hide();
+ SweetAlert.swal('Success!', 'Hybrid drop is done successfully', 'success');
+ location.reload();
+ }, function (e) {
+ loadingRequest.hide();
+ if (e.data && e.data.exception) {
+ var message = e.data.exception;
+ var msg = !!(message) ? message : 'Failed to take action.';
+ SweetAlert.swal('Oops...', msg, 'error');
+ } else {
+ SweetAlert.swal('Oops...', "Failed to take action.", 'error');
+ }
+ });
+ }
+
+ });
+ };
+});
\ No newline at end of file
diff --git a/webapp/app/js/controllers/hybridInstanceSchema.js b/webapp/app/js/controllers/hybridInstanceSchema.js
new file mode 100644
index 0000000..033e3c1
--- /dev/null
+++ b/webapp/app/js/controllers/hybridInstanceSchema.js
@@ -0,0 +1,404 @@
+/*
+ * 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';
+
+KylinApp.controller('HybridInstanceSchema', function (
+ $scope, $q, $location, $interpolate, $templateCache, $routeParams,
+ CubeList, HybridInstanceService, ProjectModel, modelsManager, SweetAlert, MessageService, loadingRequest, CubeService, CubeDescService
+) {
+
+ // check for empty project of header, break the operation.
+ if (!$scope.isEdit && ProjectModel.selectedProject === null) {
+ SweetAlert.swal('Oops...', 'Please select your project first.', 'warning');
+ $location.path("/models");
+ return;
+ }
+
+ $scope.LEFT = 'LEFT';
+ $scope.RIGHT = 'RIGHT';
+ $scope.isFormDisabled = false;
+
+ $scope.cubeList = CubeList;
+ $scope.projectModel = ProjectModel;
+ $scope.modelsManager = modelsManager;
+
+ $scope.route = { params: $routeParams.hybridName };
+ $scope.isEdit = !!$routeParams.hybridName;
+
+ $scope.isEditInitialized = false;
+ $scope.isLockEditModel = false;
+
+ $scope.form = {
+ name: '',
+ model: ''
+ };
+
+ resetPageData();
+
+ /**
+ * Computed: get the model's cubes
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ */
+ $scope.getFiltedModelCube = function(dir) {
+ var dataRows = $scope.table[dir].dataRows;
+
+ return dataRows.filter(function(row) {
+ return row.model === $scope.form.model;
+ });
+ }
+
+ /**
+ * Computed: get the count of the model cubes
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ */
+ $scope.getFiltedModelCubeCount = function(dir) {
+ return $scope.getFiltedModelCube(dir).length;
+ }
+
+ /**
+ * Computed: judge that current cube row is checked
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ * @param {*} cube
+ */
+ $scope.isCubeChecked = function(dir, cube) {
+ return $scope.table[dir].checkedCubeIds.indexOf(function(cubeId) {
+ return cubeId === cube.uuid;
+ }) !== -1;
+ };
+
+ /**
+ * Computed: judge that all rows of the table are checked
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ */
+ $scope.isCheckAll = function(dir) {
+ var dataRows = $scope.getFiltedModelCube(dir);
+
+ return dataRows.length ? dataRows.every(function(row) {
+ return row.isChecked === true;
+ }) : false;
+ };
+
+ $scope.toggleCube = function(cube) {
+ cube.isChecked = !cube.isChecked;
+ }
+
+ /**
+ * Computed: judge that model select component can be chosen
+ */
+ $scope.isModelSelectDisabled = function() {
+ return !modelsManager.models.length
+ || $scope.table[$scope.RIGHT].dataRows.length;
+ }
+
+ /**
+ * Computed: judge is form valid
+ */
+ $scope.isFormValid = function() {
+ // get schema data
+ var schema = getSchema();
+
+ return Object.keys(schema).every(function(key) {
+ // Array.length for checking select cubes count >= 2
+ // otherwise checking empty value
+ return schema[key] instanceof Array ? schema[key].length > 1 : schema[key];
+ });
+ };
+
+ /**
+ * Action: toggle all rows' check status of the table
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ * @param {undefined | boolean} toStatus: for change all the table's cubes hardly
+ */
+ $scope.toggleAll = function(dir, toStatus) {
+ var isCheckAll = $scope.isCheckAll(dir);
+ var dataRows = $scope.getFiltedModelCube(dir);
+
+ dataRows.forEach(function(row) {
+ if(toStatus !== undefined) {
+ row.isChecked = toStatus;
+ } else {
+ row.isChecked = !isCheckAll;
+ }
+ });
+ };
+
+ /**
+ * Action: transfer checked rows from destination table to source table
+ *
+ * @param {'LEFT' | 'RIGHT'} dir
+ */
+ $scope.transferTo = function(dir) {
+ var toDir = dir;
+ var fromDir = dir === $scope.RIGHT ? $scope.LEFT : $scope.RIGHT;
+ var srcTable = $scope.table[fromDir];
+ var disTable = $scope.table[toDir];
+
+ // get checked rows from source table to transfer rows
+ var transferRows = srcTable.dataRows.filter(function(row) {
+ return row.isChecked;
+ });
+
+ // filter unchecked row to source table rows
+ srcTable.dataRows = srcTable.dataRows.filter(function(row) {
+ return !row.isChecked;
+ });
+
+ // clean transfer rows check status
+ transferRows.forEach(function(row) {
+ row.isChecked = false;
+ });
+
+ // push transfer rows to destination table
+ disTable.dataRows = disTable.dataRows.concat(transferRows);
+ }
+
+ /**
+ * Action: page edit cancel handler
+ */
+ $scope.cancel = function() {
+ history.go(-1);
+ };
+
+ /**
+ * Action: page edit submit handler
+ */
+ $scope.submit = function() {
+ // get form data
+ var schema = getSchema();
+ // show save warning
+ saveWarning(function() {
+ // show loading
+ loadingRequest.show();
+ // save the hybrid cube
+ if(!$scope.isEdit) {
+ HybridInstanceService.save({}, schema, successHandler, failedHandler);
+ } else {
+ HybridInstanceService.update({}, schema, successHandler, failedHandler);
+ }
+ });
+
+ function successHandler(request) {
+ if(request.successful === false) {
+ var message = request.message;
+ var msg = !!message ? message : 'Failed to take action.';
+ var template = hybridInstanceResultTmpl({ text: msg, schema: schema });
+ MessageService.sendMsg(template, 'error', {}, true, 'top_center');
+ } else {
+ if($scope.isEdit) {
+ SweetAlert.swal('', 'Update hybrid cube successfully.', 'success');
+ } else {
+ SweetAlert.swal('', 'Create hybrid cube successfully.', 'success');
+ }
+ $location.path('/models');
+ }
+ // hide global loading
+ loadingRequest.hide();
+ }
+
+ function failedHandler(e) {
+ if (e.data && e.data.exception) {
+ var message = e.data.exception;
+ var msg = !!(message) ? message : 'Failed to take action.';
+ var template = hybridInstanceResultTmpl({ text: msg, schema: schema });
+ MessageService.sendMsg(template, 'error', {}, true, 'top_center');
+ } else {
+ var template = hybridInstanceResultTmpl({ text: 'Failed to take action.', schema: schema });
+ MessageService.sendMsg(template, 'error', {}, true, 'top_center');
+ }
+ // hide global loading
+ loadingRequest.hide();
+ }
+ }
+
+ doPerpare();
+
+ /**
+ * Init: initialize watcher
+ */
+ function doPerpare() {
+ $scope.$watch('projectModel.selectedProject', function (newValue, oldValue) {
+ if (newValue != oldValue || newValue == null) {
+ CubeList.removeAll();
+ resetPageData();
+ listModels();
+ }
+ });
+
+ $scope.$watch('modelsManager.models', function() {
+ $scope.form.model = modelsManager.models[0] && modelsManager.models[0].name || '';
+ });
+
+ $scope.$watch('form.model', function() {
+ if(!$scope.isLockEditModel) {
+ cleanCubeStatus();
+ }
+ $scope.isLockEditModel = false;
+ });
+
+ $scope.$watch('cubeList.cubes', function() {
+ loadTableData();
+
+ if ($scope.isEdit && !$scope.isEditInitialized && CubeList.cubes.length) {
+ getEditHybridInstance();
+ $scope.isEditInitialized = true;
+ }
+ });
+ }
+
+ /**
+ * Helper: get form data
+ */
+ function getSchema() {
+ const schema = {
+ hybrid: $scope.form.name,
+ project: $scope.projectModel.selectedProject,
+ model: $scope.form.model,
+ cubes: $scope.table[$scope.RIGHT].dataRows.map(function(row) {
+ return row.name;
+ })
+ };
+ return schema;
+ }
+
+ /**
+ * Helper: reset page data
+ */
+ function resetPageData() {
+ $scope.table = {};
+ $scope.form.model = '';
+ $scope.table[$scope.LEFT] = {
+ dataRows: []
+ };
+ $scope.table[$scope.RIGHT] = {
+ dataRows: []
+ };
+ }
+
+ /**
+ * Helper: ajax request models
+ */
+ function listModels () {
+ var defer = $q.defer();
+ var queryParam = {};
+ if (!$scope.projectModel.isSelectedProjectValid()) {
+ defer.resolve([]);
+ return defer.promise;
+ }
+
+ if (!$scope.projectModel.projects.length) {
+ defer.resolve([]);
+ return defer.promise;
+ }
+ queryParam.projectName = $scope.projectModel.selectedProject;
+ return modelsManager.list(queryParam).then(function (resp) {
+ defer.resolve(resp);
+ modelsManager.loading = false;
+ return defer.promise;
+ });
+ };
+
+ /**
+ * Helper: clean left table and reset status
+ */
+ function loadTableData() {
+ var cubesData = Object.create($scope.cubeList.cubes);
+ var unusedCubeTable = $scope.table[$scope.LEFT].dataRows = [];
+
+ cubesData.forEach(function(cubeData) {
+ cubeData.isChecked = false;
+ unusedCubeTable.push(cubeData);
+ });
+ }
+
+ function hybridInstanceResultTmpl(notification) {
+ // Get the static notification template.
+ var tmpl = notification.type == 'success' ? 'hybridResultSuccess.html' : 'hybridResultError.html';
+ return $interpolate($templateCache.get(tmpl))(notification);
+ };
+
+ function saveWarning(callback) {
+ SweetAlert.swal({
+ title: $scope.isEdit
+ ? 'Are you sure to update the Hybrid Cube?'
+ : 'Are you sure to save the Hybrid Cube?',
+ text: $scope.isEdit
+ ? ''
+ : '',
+ type: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#DD6B55',
+ confirmButtonText: "Yes",
+ closeOnConfirm: true
+ }, function(isConfirm) {
+ if(isConfirm) {
+ callback();
+ }
+ })};
+
+ /**
+ * Helper: if $scope.form.model is changed, clean all the selected cube.
+ */
+ function cleanCubeStatus() {
+ // clean left table cubes
+ $scope.table[$scope.LEFT].dataRows.forEach(function(row) {
+ row.isChecked = false;
+ });
+ // check right table cubes
+ $scope.table[$scope.RIGHT].dataRows.forEach(function(row) {
+ row.isChecked = true;
+ });
+ // move right table cubes to left table
+ $scope.transferTo($scope.LEFT);
+ }
+
+ /**
+ * Helper: get edit hybrid cube
+ */
+ function getEditHybridInstance() {
+ loadingRequest.show();
+
+ HybridInstanceService.getByName({ hybrid_name: $routeParams.hybridName }, function (_hybridInstance) {
+ var hybridInstance = _hybridInstance.hybridInstance;
+
+ $scope.form.uuid = hybridInstance.uuid;
+ $scope.form.name = hybridInstance.name;
+
+ hybridInstance.realizations.forEach(function(realizationItem) {
+ var usedCubeName = realizationItem.realization;
+ var unusedCubeTable = $scope.table[$scope.LEFT];
+
+ unusedCubeTable.dataRows.forEach(function(row) {
+ if(row.name === usedCubeName) {
+ row.isChecked = true;
+ $scope.form.model = row.model;
+ }
+ });
+ });
+
+ $scope.transferTo($scope.RIGHT);
+ loadingRequest.hide();
+ $scope.isLockEditModel = true;
+ });
+ }
+});
\ No newline at end of file
diff --git a/webapp/app/js/directives/directives.js b/webapp/app/js/directives/directives.js
index d6ed304..1371607 100644
--- a/webapp/app/js/directives/directives.js
+++ b/webapp/app/js/directives/directives.js
@@ -27,14 +27,14 @@ KylinApp.directive('kylinPagination', function ($parse, $q) {
templateUrl: 'partials/directives/pagination.html',
link: function (scope, element, attrs) {
var _this = this;
- scope.limit = 15;
scope.hasMore = false;
scope.data = $parse(attrs.data)(scope.$parent);
scope.action = $parse(attrs.action)(scope.$parent);
scope.loadFunc = $parse(attrs.loadFunc)(scope.$parent);
+ scope.isHideTotal = $parse(attrs.isHideTotal)();
+ scope.limit = $parse(attrs.limit)() || 15;
scope.autoLoad = true;
-
scope.$watch("action.reload", function (newValue, oldValue) {
if (newValue != oldValue) {
scope.reload();
diff --git a/webapp/app/js/model/hybridInstanceManager.js b/webapp/app/js/model/hybridInstanceManager.js
new file mode 100644
index 0000000..5a86ae3
--- /dev/null
+++ b/webapp/app/js/model/hybridInstanceManager.js
@@ -0,0 +1,60 @@
+/*
+ * 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.
+*/
+
+KylinApp.service('hybridInstanceManager', function($q, HybridInstanceService, ProjectModel) {
+ var _this = this;
+ this.hybridInstances = [];
+ this.hybridInstanceNameList = [];
+
+ //tracking models loading status
+ this.loading = false;
+
+ //list hybrid cubes
+ this.list = function(queryParam) {
+ _this.loading = true;
+
+ var defer = $q.defer();
+
+ HybridInstanceService.list(queryParam, function(_hybridInstances) {
+ _hybridInstances = _hybridInstances.map(function(_hybridInstance) {
+ var instance = _hybridInstance.hybridInstance;
+ instance.project = _hybridInstance.projectName;
+ instance.model = _hybridInstance.modelName;
+
+ return instance;
+ });
+
+ angular.forEach(_hybridInstances, function(hybridInstance) {
+ _this.hybridInstanceNameList.push(hybridInstance.name);
+ // hybridInstance.project = ProjectModel.getProjectByCubeModel(hybridInstance.name);
+ });
+
+ _hybridInstances = _.filter(_hybridInstances, function(hybridInstance) {
+ return hybridInstance.name !== undefined;
+ });
+
+ _this.hybridInstances = _hybridInstances;
+ _this.loading = false;
+ },
+ function() {
+ defer.reject('Failed to load models');
+ });
+
+ return defer.promise;
+ };
+})
diff --git a/webapp/app/js/services/hybridInstance.js b/webapp/app/js/services/hybridInstance.js
new file mode 100644
index 0000000..06aed20
--- /dev/null
+++ b/webapp/app/js/services/hybridInstance.js
@@ -0,0 +1,28 @@
+/*
+ * 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.
+*/
+
+KylinApp.factory('HybridInstanceService', ['$resource', function ($resource, config) {
+ return $resource(Config.service.url + 'hybrids/:hybrid_name', {}, {
+ list: { method: 'GET', params: {}, isArray: true },
+ getByName: { method: 'GET', isArray: false },
+ drop: {method: 'DELETE', params: {}, isArray: false},
+ // clone: {method: 'PUT', params: {action: 'clone'}, isArray: false},
+ save: {method: 'POST', params: {}, isArray: false},
+ update: {method: 'PUT', params: {}, isArray: false}
+ });
+}]);
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index cf9748e..cb59fab 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -950,4 +950,110 @@ div[ng-controller="ModelConditionsSettingsCtrl"] {
width: 49%;
margin-top: 0;
}
+}
+
+.form-title {
+ font-size: 24px;
+ color: #3A87AD;
+ line-height: 1;
+ margin: 0;
+}
+
+#main {
+ min-height: 100vh;
+}
+
+div[ng-controller="HybridInstanceSchema"] {
+ .form-title {
+ margin-bottom: 43px;
+ }
+ .control-label {
+ margin-top: 7px;
+ }
+ .split-line {
+ border-bottom: 1px solid #DDDDDD;
+ margin: 5px 0 20px 0;
+ }
+ .table {
+ margin-bottom: 0;
+ }
+ .table-header {
+ box-sizing: border-box;
+ border: 1px solid #DDDDDD;
+ border-bottom: none;
+ }
+ .transter-actions {
+ position: relative;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ .transter-action-item {
+ text-align: center;
+ margin-bottom: 10px;
+ }
+ .transter-action-item i {
+ width: 32px;
+ height: 32px;
+ display: inline-block;
+ cursor: pointer;
+ background: #FFFFFF;
+ border: 1px solid #DDDDDD;
+ border-radius: 2px;
+ padding-top: 7px;
+ color: #808080;
+ font-size: 16px;
+ &:hover {
+ border-color: #3A87AD;
+ color: #3A87AD;
+ }
+ }
+ .data-empty {
+ text-align: center;
+ font-size: 14px;
+ color: #808080;
+ line-height: 22px;
+ padding: 8px 0;
+ border: 1px solid #DDDDDD;
+ }
+ .col-xs-5 {
+ z-index: 1;
+ }
+ .fix-height-table {
+ max-height: 408px;
+ overflow: auto;
+ }
+ .edit-operator {
+ position: relative;
+ margin-bottom: 30px;
+ }
+ div[tooltip] + .tooltip {
+ white-space: nowrap;
+ .tooltip-inner {
+ max-width: 1000px;
+ }
+ }
+ .table-checkbox {
+ text-align: center;
+ width: 38px;
+ height: 38px;
+ img {
+ cursor: pointer;
+ }
+ }
+ .status-center {
+ text-align: center;
+ width: 150px;
+ }
+}
+
+.kylin-icon-hybrid {
+ margin-right: 10px;
+}
+.col-xs-offset-5 {
+ margin-left: 41.66666667%;
+}
+
+.text-info {
+ font-size: 18px;
+ color: #263238;
}
\ No newline at end of file
diff --git a/webapp/app/less/build.less b/webapp/app/less/build.less
index 4271bac..241eee8 100644
--- a/webapp/app/less/build.less
+++ b/webapp/app/less/build.less
@@ -20,3 +20,4 @@
@import 'app.less';
@import 'component.less';
@import 'animation.less';
+@import 'font.less';
\ No newline at end of file
diff --git a/webapp/app/less/font.less b/webapp/app/less/font.less
new file mode 100644
index 0000000..1f5484d
--- /dev/null
+++ b/webapp/app/less/font.less
@@ -0,0 +1,36 @@
+@font-face {
+ font-family: 'kylin';
+ src: url('../fonts/kylin.eot?pdayeh');
+ src: url('fonts/kylin.eot?pdayeh#iefix') format('embedded-opentype'),
+ url('../fonts/kylin.ttf?pdayeh') format('truetype'),
+ url('../fonts/kylin.woff?pdayeh') format('woff'),
+ url('../fonts/kylin.svg?pdayeh#kylin') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+[class^='kylin-icon-'],
+[class*=' kylin-icon-'] {
+ /* use !important to prevent issues with browser extensions that change fonts */
+ font-family: 'kylin' !important;
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.kylin-icon-hybrid:before {
+ content: '\e900';
+}
+.kylin-icon-arrows_right:before {
+ content: '\e901';
+}
+.kylin-icon-arrows_left:before {
+ content: '\e902';
+}
diff --git a/webapp/app/partials/cubes/hybrid_edit.html b/webapp/app/partials/cubes/hybrid_edit.html
new file mode 100644
index 0000000..21b130f
--- /dev/null
+++ b/webapp/app/partials/cubes/hybrid_edit.html
@@ -0,0 +1,176 @@
+<!--
+* 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.
+-->
+
+<div ng-controller="CubesCtrl">
+ <div class="page-header" style="height: 50px;">
+ <!--Project-->
+ <form class="navbar-form navbar-left" style="margin-top: 0px !important;" ng-if="userService.isAuthorized()">
+ <div class="form-group">
+ <a class="btn btn-xs btn-info" href="projects" tooltip="Manage Project">
+ <i class="fa fa-gears"></i>
+ </a>
+ <a class="btn btn-xs btn-primary" ng-if="userService.hasRole('ROLE_ADMIN')" style="width: 29px" tooltip="Add Project" ng-click="toCreateProj()">
+ <i class="fa fa-plus"></i>
+ </a>
+ </div>
+ </form>
+ </div>
+
+ <div class="row" ng-controller="HybridInstanceSchema">
+ <div class="col-xs-12">
+ <form role="form" name="hybrid_cube_form form-inline" novalidate>
+
+ <h1 class="form-title">Hybrid Designer</h1>
+
+ <div class="row">
+ <div class="col-xs-5 form-group">
+ <div class="row">
+ <label class="col-xs-3 control-label no-padding-right font-color-default" for="hybridName">Hybrid Name</label>
+ <div class="col-xs-9">
+ <input type="text" class="form-control" id="hybridName" placeholder="Hybrid Name" ng-disabled="isFormDisabled || isEdit" ng-model="form.name">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="split-line"></div>
+
+ <div class="row edit-operator">
+ <div class="col-xs-5">
+ <div class="dataTables_wrapper no-footer">
+ <div class="row table-header">
+ <label class="col-xs-2 control-label no-padding-right font-color-default" for="modelName">Model</label>
+ <div class="col-xs-10">
+ <div ng-show="!isModelSelectDisabled()">
+ <select width="'100%'" chosen ng-model="form.model" ng-change="toggleAll(LEFT, false)" ng-options="model.name as model.name for model in modelsManager.models" ng-disabled="isModelSelectDisabled()"></select>
+ </div>
+ <div tooltip="If you want to switch model, remove the selected cubes." ng-show="isModelSelectDisabled()">
+ <select width="'100%'" chosen ng-model="form.model" ng-change="toggleAll(LEFT, false)" ng-options="model.name as model.name for model in modelsManager.models" ng-disabled="isModelSelectDisabled()"></select>
+ </div>
+ </div>
+ </div>
+ <div class="row fix-height-table">
+ <table class="table table-striped table-bordered table-hover dataTable no-footer ng-scope" ng-if="getFiltedModelCubeCount(LEFT)">
+ <thead>
+ <tr>
+ <th class="table-checkbox">
+ <img ng-if="!isCheckAll(LEFT)" src="image/checkbox-.svg" ng-click="toggleAll(LEFT)" />
+ <img ng-if="isCheckAll(LEFT)" src="image/checkbox+.svg" ng-click="toggleAll(LEFT)" />
+ </th>
+ <th>Name</th>
+ <th class="status-center">Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="cube in table[LEFT].dataRows" ng-if="cube.model === form.model" ng-click="toggleCube(cube)" style="cursor: pointer;">
+ <td class="table-checkbox">
+ <img ng-if="!cube.isChecked" src="image/checkbox-.svg" />
+ <img ng-if="cube.isChecked" src="image/checkbox+.svg" />
+ </td>
+ <td>{{ cube.name }}</td>
+ <td class="status-center">
+ <span class="label" ng-class="{'label-success': cube.status=='READY', 'label-default': cube.status=='DISABLED', 'label-warning': cube.status=='DESCBROKEN'}">
+ {{ cube.status }}
+ </span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div class="data-empty" ng-if="!getFiltedModelCubeCount(LEFT)">
+ Empty
+ </div>
+
+ <div class="row">
+ <div class="col-xs-12">
+ <kylin-pagination data="cubeList.cubes" load-func="list" action="action" is-hide-total="true" limit="999999" />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-xs-2 col-lg-1"></div>
+
+ <div style="position: absolute; height: 100%; width: 100%;">
+ <div class="col-xs-offset-5 col-xs-2 col-lg-1 transter-actions">
+ <div class="transter-action-item">
+ <i class="kylin-icon-arrows_right" ng-click="transferTo(RIGHT)"></i>
+ </div>
+ <div class="transter-action-item">
+ <i class="kylin-icon-arrows_left" ng-click="transferTo(LEFT)"></i>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-xs-5">
+ <div class="dataTables_wrapper no-footer">
+ <div class="row table-header">
+ <label class="col-xs-12 control-label no-padding-right">Selected Cubes: {{table[RIGHT].dataRows.length}}</label>
+ </div>
+ <div class="row fix-height-table">
+ <table class="table table-striped table-bordered table-hover dataTable no-footer ng-scope" ng-if="table[RIGHT].dataRows.length">
+ <thead>
+ <tr>
+ <th class="table-checkbox">
+ <img ng-if="!isCheckAll(RIGHT)" src="image/checkbox-.svg" ng-click="toggleAll(RIGHT)" />
+ <img ng-if="isCheckAll(RIGHT)" src="image/checkbox+.svg" ng-click="toggleAll(RIGHT)" />
+ </th>
+ <th>Name</th>
+ <th class="status-center">Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="cube in table[RIGHT].dataRows" ng-click="toggleCube(cube)" style="cursor: pointer;">
+ <td class="table-checkbox">
+ <img ng-if="!cube.isChecked" src="image/checkbox-.svg" />
+ <img ng-if="cube.isChecked" src="image/checkbox+.svg" />
+ </td>
+ <td>{{ cube.name }}</td>
+ <td class="status-center">
+ <span class="label" ng-class="{'label-success': cube.status=='READY', 'label-default': cube.status=='DISABLED', 'label-warning': cube.status=='DESCBROKEN'}">
+ {{ cube.status }}
+ </span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div class="data-empty" ng-if="!table[RIGHT].dataRows.length">
+ Empty
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="split-line"></div>
+
+ <div class="row">
+ <div class="col-xs-12">
+ <div class="pull-right">
+ <button class="btn btn-sm btn-default" ng-click="cancel()">Cancel</button>
+ <button class="btn btn-sm btn-primary" ng-click="submit()" ng-disabled="!isFormValid()">Submit</button>
+ </div>
+ </div>
+ </div>
+
+ </form>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/webapp/app/partials/directives/pagination.html b/webapp/app/partials/directives/pagination.html
index dba9fae..d8fd242 100644
--- a/webapp/app/partials/directives/pagination.html
+++ b/webapp/app/partials/directives/pagination.html
@@ -21,7 +21,7 @@
<i class="icon-plus icon-white"></i> <span>{{ loaded ? 'More' : 'Loading...' }}</span>
</button>
<div class="clearfix" style="margin: 5px"></div>
- <div class="pull-left" style="padding-right: 3%">
+ <div class="pull-left" style="padding-right: 3%" ng-if="!isHideTotal">
<span class="pull-left font-color-default" style="font-size: 15px"><strong>Total: {{getLength(data)}}</strong></span>
</div>
</div>
diff --git a/webapp/app/partials/models/models_tree.html b/webapp/app/partials/models/models_tree.html
index 4a91b00..d2064a2 100644
--- a/webapp/app/partials/models/models_tree.html
+++ b/webapp/app/partials/models/models_tree.html
@@ -17,57 +17,86 @@
-->
<div class="tree-border">
- <div class="row">
- <div class="col-xs-12" style="margin-top:10px;">
- <!--<i class="fa fa-plus fa-2x" style="color:green;"> New</i>-->
- <a ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)" class="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="true">
- <i class="fa fa-plus fa-2x" style="color:#2e8965;"> New<span class="caret"></span></i>
- <!--<i> New </i> <span class="caret"></span>-->
- </a>
- <ul class="dropdown-menu">
- <li ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)">
- <a href="models/add"><i class="fa fa-star"></i>New Model</a>
- </li>
- <li ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)">
- <a href="cubes/add"><i class="fa fa-cube"></i>New Cube</a>
- </li>
+ <div class="row">
+ <div class="col-xs-12" style="margin-top:10px;">
+ <!--<i class="fa fa-plus fa-2x" style="color:green;"> New</i>-->
+ <a ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)" class="dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="true">
+ <i class="fa fa-plus fa-2x" style="color:#2e8965;"> New<span class="caret"></span></i>
+ <!--<i> New </i> <span class="caret"></span>-->
+ </a>
+ <ul class="dropdown-menu">
+ <li ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)">
+ <a href="models/add"><i class="fa fa-star"></i>New Model</a>
+ </li>
+ <li ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)">
+ <a href="cubes/add"><i class="fa fa-cube"></i>New Cube</a>
+ </li>
+ <li ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission('project',projectModel, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)">
+ <a href="hybrid/add"><i class="kylin-icon-hybrid"></i>New Hybrid</a>
+ </li>
+ </ul>
+ </div>
- </ul>
- </div>
+ </div>
+ <div class="space-4 box-header with-border"></div>
+ <!--tree-->
- </div>
- <div class="space-4 box-header with-border"></div>
- <!--tree-->
<div>
- <h3 class="text-info">Models</h3>
+ <h3 class="text-info">Models ({{modelsManager.models.length}})</h3>
</div>
-<!--{{window}}px -->
- <div id="cube_model_trees" style="width:100%; height:250px; overflow:auto;margin-top: 20px;" class="cube_model_trees">
+ <!--{{window}}px -->
+ <div id="cube_model_trees" style="width:100%; height:250px; overflow:auto;margin-top: 20px;" class="cube_model_trees">
- <ul class="list-group models-tree" id="models-tree">
- <li class="list-group-item" ng-repeat="model in modelsManager.models">
+ <ul class="list-group models-tree" id="models-tree">
+ <li class="list-group-item" ng-repeat="model in modelsManager.models">
- <div class="pull-right" showonhoverparent style="display:none;" >
- <div ng-click="$event.stopPropagation();" class="btn-group">
- <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
- Action <span class="ace-icon fa fa-caret-down icon-on-right"></span>
- </button>
- <ul class="dropdown-menu" role="menu" style="right:0;left:auto;" ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('model',model, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))">
- <li><a ng-click="editModel(model, false)" title="Edit Model" style="cursor:pointer;margin-right: 8px;" >Edit</a></li>
- <li><a ng-click="cloneModel(model)" title="Clone Model" style="cursor:pointer;margin-right: 8px;" >Clone </a></li>
- <li><a ng-click="dropModel(model)" title="Drop Model" style="cursor:pointer;margin-right: 8px;">Drop</a></li>
- <li ng-if="userService.hasRole('ROLE_ADMIN')">
- <a ng-click="editModel(model, true)">Edit(JSON)</a></li>
- </ul>
- </div>
+ <div class="pull-right" showonhoverparent style="display:none;" >
+ <div ng-click="$event.stopPropagation();" class="btn-group">
+ <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
+ Action <span class="ace-icon fa fa-caret-down icon-on-right"></span>
+ </button>
+ <ul class="dropdown-menu" role="menu" style="right:0;left:auto;" ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('model',model, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))">
+ <li><a ng-click="editModel(model, false)" title="Edit Model" style="cursor:pointer;margin-right: 8px;" >Edit</a></li>
+ <li><a ng-click="cloneModel(model)" title="Clone Model" style="cursor:pointer;margin-right: 8px;" >Clone </a></li>
+ <li><a ng-click="dropModel(model)" title="Drop Model" style="cursor:pointer;margin-right: 8px;">Drop</a></li>
+ <li ng-if="userService.hasRole('ROLE_ADMIN')">
+ <a ng-click="editModel(model, true)">Edit(JSON)</a></li>
+ </ul>
</div>
- <span class="strong"><a style="cursor: pointer;word-break:break-all;" ng-click="openModal(model)">{{model.name}}</a></span>
+ </div>
+ <span class="strong"><a style="cursor: pointer;word-break:break-all;" ng-click="openModal(model)">{{model.name}}</a></span>
+
+ </li>
+ </ul>
+ <div ng-if="modelsManager.loading==true"><i class="fa fa-2x fa-spinner fa-spin"></i> Loading..</div>
+ <div no-result ng-if="modelsManager.loading!=true&&modelsManager.models.length==0"></div>
+ </div>
- </li>
- </ul>
- <div ng-if="modelsManager.loading==true"><i class="fa fa-2x fa-spinner fa-spin"></i> Loading..</div>
- <div no-result ng-if="modelsManager.loading!=true&&modelsManager.models.length==0"></div>
+ <div ng-controller="HybridInstanceCtrl">
+ <div>
+ <h3 class="text-info">Hybrids ({{hybridInstanceManager.hybridInstances.length}})</h3>
+ </div>
+ <div id="hybrid_cube_trees" style="width:100%; height:250px; overflow:auto;margin-top: 20px;" class="cube_model_trees">
+ <ul class="list-group models-tree" id="hybrid-tree">
+ <li class="list-group-item" ng-repeat="hybridInstance in hybridInstanceManager.hybridInstances">
+ <div class="pull-right" showonhoverparent style="display:none;" >
+ <div ng-click="$event.stopPropagation();" class="btn-group">
+ <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
+ Action <span class="ace-icon fa fa-caret-down icon-on-right"></span>
+ </button>
+ <ul class="dropdown-menu" role="menu" style="right:0;left:auto;" ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('model',model, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))">
+ <li><a ng-click="editHybridInstance(hybridInstance)" title="Edit Hybrid" style="cursor:pointer;margin-right: 8px;" >Edit</a></li>
+ <li><a ng-click="dropHybridInstance(hybridInstance)" title="Drop Hybrid" style="cursor:pointer;margin-right: 8px;">Drop</a></li>
+ </ul>
+ </div>
+ </div>
+ <span class="strong"><a style="cursor: pointer;word-break:break-all;" ng-click="openHybridInstance(hybridInstance)">{{hybridInstance.name}}</a></span>
+ </li>
+ </ul>
+ <div ng-if="hybridInstanceManager.loading === true"><i class="fa fa-2x fa-spinner fa-spin"></i> Loading..</div>
+ <div no-result ng-if="hybridInstanceManager.loading!==true && hybridInstanceManager.hybridInstances.length === 0"></div>
</div>
+ </div>
</div>
<div ng-include="'partials/models/model_detail.html'"></div>
diff --git a/webapp/app/routes.json b/webapp/app/routes.json
index 65140a0..eefe35b 100644
--- a/webapp/app/routes.json
+++ b/webapp/app/routes.json
@@ -120,5 +120,19 @@
"tab": "dashboard",
"controller": "DashboardCtrl"
}
+ },
+ {
+ "url": "/hybrid/add",
+ "params": {
+ "templateUrl": "partials/cubes/hybrid_edit.html",
+ "tab": "models"
+ }
+ },
+ {
+ "url": "/hybrid/edit/:hybridName",
+ "params": {
+ "templateUrl": "partials/cubes/hybrid_edit.html",
+ "tab": "models"
+ }
}
]