Commit 8206aa7f authored by Nabil Adouani's avatar Nabil Adouani

#908 Add a text dashboard widget

parent 632b339f
......@@ -186,6 +186,7 @@
<script src="scripts/directives/dashboard/item.js"></script>
<script src="scripts/directives/dashboard/line.js"></script>
<script src="scripts/directives/dashboard/multiline.js"></script>
<script src="scripts/directives/dashboard/text.js"></script>
<script src="scripts/directives/dateTimePicker.js"></script>
<script src="scripts/directives/dt-picker.js"></script>
<script src="scripts/directives/entityLink.js"></script>
......
......@@ -57,11 +57,11 @@
this.canEditDashboard = function() {
return (this.createdBy === this.currentUser.id) ||
(this.dashboardStatus = 'Shared' && AuthenticationSrv.isAdmin(this.currentUser));
}
};
this.options = {
dashboardAllowedTypes: ['container'],
containerAllowedTypes: ['bar', 'line', 'donut', 'counter', 'multiline'],
containerAllowedTypes: ['bar', 'line', 'donut', 'counter', 'text', 'multiline'],
maxColumns: 3,
cls: DashboardSrv.typeClasses,
labels: {
......@@ -70,6 +70,7 @@
donut: 'Donut',
line: 'Line',
counter: 'Counter',
text: 'Text',
multiline: 'Multi Lines'
},
editLayout: !_.find(this.definition.items, function(row) {
......@@ -132,9 +133,9 @@
}, 0);
});
}
};
this.itemInserted = function(item, rows, rowIndex, index) {
this.itemInserted = function(item, rows/*, rowIndex, index*/) {
if(!item.id){
item.id = UtilsSrv.guid();
}
......
......@@ -33,7 +33,7 @@
}
return s;
}
};
scope.buildSerie = function(serie, q, index) {
return {
......@@ -166,7 +166,7 @@
};
scope.chart = chart;
}, function(err) {
}, function(/*err*/) {
scope.error = true;
NotificationSrv.log('Failed to fetch data, please edit the widget definition', 'error');
});
......
(function() {
'use strict';
angular.module('theHiveDirectives').directive('dashboardText', function($q, $http, $state, DashboardSrv, GlobalSearchSrv, NotificationSrv) {
return {
restrict: 'E',
scope: {
filter: '=?',
options: '=',
entity: '=',
autoload: '=',
mode: '=',
refreshOn: '@',
resizeOn: '@',
metadata: '='
},
templateUrl: 'views/directives/dashboard/text/view.html',
link: function(scope, elem) {
scope.error = false;
scope.data = null;
scope.globalQuery = null;
scope.load = function() {
if(!scope.options.series || scope.options.series.length === 0) {
scope.error = true;
return;
}
var query = DashboardSrv.buildChartQuery(scope.filter, scope.options.query);
scope.globalQuery = query;
var stats = {
stats: _.map(scope.options.series || [], function(serie, index) {
var s = {
_agg: serie.agg,
_name: serie.name || 'agg_' + (index + 1),
_query: serie.query || {}
};
if(serie.agg !== 'count') {
s._field = serie.field;
}
return {
model: serie.entity,
query: query,
stats: [s]
};
})
};
var statsPromise = $http.post('./api/_stats', stats);
statsPromise.then(function(response) {
scope.error = false;
scope.data = response.data;
var template = scope.options.template;
Object.keys(scope.data).forEach(function(key){
var regex = new RegExp('{{' + key + '}}', 'gi');
template = template.replace(regex, scope.data[key]);
});
scope.content = template;
}, function(/*err*/) {
scope.error = true;
NotificationSrv.log('Failed to fetch data, please edit the widget definition', 'error');
});
};
scope.copyHTML = function() {
var html = elem[0].querySelector('.widget-content').innerHTML;
function listener(e) {
e.clipboardData.setData('text/html', html);
e.clipboardData.setData('text/plain', html);
e.preventDefault();
}
document.addEventListener('copy', listener);
document.execCommand('copy');
document.removeEventListener('copy', listener);
}
if (scope.autoload === true) {
scope.load();
}
if (!_.isEmpty(scope.refreshOn)) {
scope.$on(scope.refreshOn, function(event, filter) {
scope.filter = filter;
scope.load();
});
}
}
};
});
})();
......@@ -74,7 +74,8 @@
donut: 'fa-pie-chart',
line: 'fa-line-chart',
multiline: 'fa-area-chart',
counter: 'fa-calculator'
counter: 'fa-calculator',
text: 'fa-file'
};
this.sortOptions = [{
......@@ -113,6 +114,14 @@
entity: null
}
},
{
type: 'text',
options: {
title: null,
template: null,
entity: null
}
},
{
type: 'donut',
options: {
......@@ -234,6 +243,7 @@
this.hasMinimalConfiguration = function(component) {
switch (component.type) {
case 'multiline':
case 'text':
return component.options.series.length === _.without(_.pluck(component.options.series, 'entity'), undefined).length;
default:
return !!component.options.entity;
......
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">
<i class="mr-xxs fa" ng-class="typeClasses[component.type]"></i>{{component.options.title || 'No title'}} {{mode}}
<i class="mr-xxs fa" ng-class="typeClasses[component.type]"></i>{{component.options.title || 'No title'}}
</h3>
<div class="box-tools pull-right">
......@@ -56,6 +56,14 @@
resize-on="{{resizeOn}}"
mode="mode"></dashboard-multiline>
<dashboard-text ng-switch-when="text"
entity="metadata[component.options.entity]"
options="component.options"
filter="filter"
autoload="autoload"
refresh-on="{{refreshOn}}"
mode="mode"></dashboard-text>
<dashboard-counter ng-switch-when="counter"
entity="metadata[component.options.entity]"
options="component.options"
......
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" placeholder="Ex: Summary" ng-model="component.options.title">
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label>Template</label>
<textarea class="content-box" name="template"
placeholder="Close summary" markdown-editor="{
'fullscreen': {enable: false},
'iconlibrary': 'fa',
addExtraButtons: true,
resize: 'vertical'}" rows="6" ng-model="component.options.template" required></textarea>
</div>
</div>
</div>
<uib-tabset class="nav-tabs-custom" active="layout.activeTab">
<uib-tab index="0">
<uib-tab-heading>
<i class="fa fa-bars"></i> Basic
</uib-tab-heading>
<ng-include src="'views/directives/dashboard/text/basic.html'"></ng-include>
</uib-tab>
<uib-tab index="1">
<uib-tab-heading>
<i class="fa fa-sort"></i> Series
</uib-tab-heading>
<ng-include src="'views/directives/dashboard/text/series.html'"></ng-include>
</uib-tab>
</uib-tabset>
<div ng-if="serie.filters.length > 0">
<strong>Serie's filter</strong>
</div>
<div class="row mb-xxxs" ng-repeat="filter in serie.filters track by $index">
<div class="col-sm-4">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="removeSerieFilter(serie, $index)">
<i class="fa fa-times text-danger"></i>
</button>
</span>
<select class="form-control" ng-model="filter.field"
ng-options="item.name as item.name for (key, item) in metadata[serie.entity].attributes"
ng-change="setFilterField(filter, serie.entity)"></select>
</div>
</div>
<div class="col-sm-8">
<filter-editor metadata="metadata" filter="filter" entity="serie.entity"></filter-editor>
</div>
</div>
<div class="mt-xxs">
<a href ng-click="addSerieFilter(serie)">
<i class="fa fa-plus"></i> Add a filter
</a>
</div>
<div ng-if="!component.options.series || component.options.series.length === 0" class="empty-message">
No series defined. <a href ng-click="addSerie()">Add a serie</a>
</div>
<div class="mb-xxxs dashboard-serie" ng-repeat="serie in component.options.series track by $index">
<div class="form-inline mb-xxxs">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="removeSerie($index)">
<i class="fa fa-times text-danger"></i>
</button>
</span>
<div class="form-group">
<select class="form-control" ng-model="serie.entity"
ng-options="item as metadata[item].label for item in metadata.entities"></select>
</div>
</div>
<div class="form-group">
<select class="form-control" ng-model="serie.agg"
ng-options="item.label as item.id for (key, item) in aggregations"
ng-change="setSerieAgg(serie)">
<option value="" disabled selected></option>
</select>
</div>
<div class="form-group">
<select class="form-control" ng-model="serie.field" ng-disabled="serie.agg === 'count'"
ng-options="item.name as item.name for (key, item) in fieldsForAggregation(metadata[serie.entity].attributes, serie.agg)">
<option value="" disabled selected>-- Select field --</option>
</select>
</div>
<div class="form-group">
<input class="form-control" type="text" ng-model="serie.name" placeholder="Variable name">
</div>
</div>
<div class="ml-m mt-xs">
<ng-include src="'views/directives/dashboard/text/serie.filters.html'"></ng-include>
</div>
</div>
<div ng-if="component.options.series && component.options.series.length > 0" class="mv-xs">
<a href ng-click="addSerie()">
<i class="fa fa-plus"></i> Add a serie
</a>
</div>
<div class="c3-container">
<div class="c3-error" ng-if="error">
<div class="text-center">
<h4 class="text-danger">
<div class="mb-xxs"><i class="fa fa-2x fa-exclamation-triangle"></i></div>
<div>Failed to fetch data, please edit the widget definition</div>
</h4>
</div>
</div>
<div ng-if="!error" class="row">
<div class="col-sm-12">
<div class="clearfix" ng-show="content">
<a class="pull-right ml-xxs" href ng-click="copyHTML()"><i class="fa fa-file-text-o mr-xxxs"></i>HTML</a>
<strong class="text-primary pull-right">Copy</strong>
</div>
<div marked="content" class="markdown widget-content"></div>
</div>
</div>
</div>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment