Merge remote-tracking branch 'origin/pro'

This commit is contained in:
Torkel Ödegaard 2015-02-05 10:37:41 +01:00
commit 861e45aedf
147 changed files with 3561 additions and 973 deletions

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
node_modules
coverage/
.aws-config.json
dist
/dist
/tmp
# locally required config files
web.config

View File

@ -11,6 +11,22 @@ module.exports = function (grunt) {
docsDir: 'docs/'
};
config.mode = grunt.option('mode') || 'standalone';
config.modeOptions = {
zipSuffix: '',
requirejs: {
paths: { config: '../config.sample' },
excludeConfig: true,
}
};
if (config.mode === 'backend') {
grunt.log.writeln('Setting backend build mode');
config.modeOptions.zipSuffix = '-backend';
config.modeOptions.requirejs.paths = {};
config.modeOptions.requirejs.excludeConfig = false;
}
// load plugins
require('load-grunt-tasks')(grunt);
@ -34,5 +50,4 @@ module.exports = function (grunt) {
// pass the config to grunt
grunt.initConfig(config);
};
};

View File

@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "1.9.1",
"version": "2.0.0-alpha",
"repository": {
"type": "git",
"url": "http://github.com/torkelo/grafana.git"
@ -16,7 +16,7 @@
"grunt-angular-templates": "^0.5.5",
"grunt-cli": "~0.1.13",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-compress": "~0.13.0",
"grunt-contrib-concat": "^0.4.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.5.0",
@ -33,7 +33,7 @@
"grunt-ng-annotate": "^0.9.2",
"grunt-ngmin": "0.0.3",
"grunt-string-replace": "~0.2.4",
"grunt-usemin": "^2.1.1",
"grunt-usemin": "3.0.0",
"jshint-stylish": "~0.1.5",
"karma": "~0.12.21",
"karma-chrome-launcher": "~0.1.4",
@ -65,6 +65,7 @@
"dependencies": {
"grunt-jscs": "^0.8.1",
"karma-sinon": "^1.0.3",
"lodash": "^2.4.1",
"sinon": "^1.10.3"
}
}

View File

@ -49,8 +49,7 @@ function (angular, $, _, appLevelRequire, config) {
return module;
};
app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider.otherwise({ redirectTo: config.default_route });
app.config(function($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
// this is how the internet told me to dynamically add modules :/
register_fns.controller = $controllerProvider.register;
register_fns.directive = $compileProvider.directive;
@ -68,7 +67,16 @@ function (angular, $, _, appLevelRequire, config) {
'pasvaz.bindonce'
];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters'];
if (window.grafanaBackend) {
module_types.push('routes');
angular.module('grafana.routes.standalone', []);
}
else {
module_types.push('routes.standalone');
angular.module('grafana.routes', []);
}
_.each(module_types, function (type) {
var module_name = 'grafana.'+type;
@ -85,7 +93,8 @@ function (angular, $, _, appLevelRequire, config) {
'directives/all',
'filters/all',
'components/partials',
'routes/standalone/default',
'routes/standalone/all',
'routes/backend/all',
];
_.each(config.plugins.dependencies, function(dep) {

View File

@ -0,0 +1,12 @@
define([
'settings',
],
function (Settings) {
"use strict";
var bootData = window.grafanaBootData;
var options = bootData.settings;
return new Settings(options);
});

View File

@ -6,6 +6,8 @@ function () {
function PanelMeta(options) {
this.description = options.description;
this.fullscreen = options.fullscreen;
this.editIcon = options.editIcon;
this.panelName = options.panelName;
this.menu = [];
this.editorTabs = [];
this.extendedMenu = [];

View File

@ -0,0 +1,100 @@
require.config({
urlArgs: 'bust=' + (new Date().getTime()),
baseUrl: 'public/app',
paths: {
config: 'components/config',
settings: 'components/settings',
kbn: 'components/kbn',
store: 'components/store',
css: '../vendor/require/css',
text: '../vendor/require/text',
moment: '../vendor/moment',
filesaver: '../vendor/filesaver',
angular: '../vendor/angular/angular',
'angular-route': '../vendor/angular/angular-route',
'angular-sanitize': '../vendor/angular/angular-sanitize',
'angular-dragdrop': '../vendor/angular/angular-dragdrop',
'angular-strap': '../vendor/angular/angular-strap',
timepicker: '../vendor/angular/timepicker',
datepicker: '../vendor/angular/datepicker',
bindonce: '../vendor/angular/bindonce',
crypto: '../vendor/crypto.min',
spectrum: '../vendor/spectrum',
lodash: 'components/lodash.extended',
'lodash-src': '../vendor/lodash',
bootstrap: '../vendor/bootstrap/bootstrap',
jquery: '../vendor/jquery/jquery-2.1.1.min',
'extend-jquery': 'components/extend-jquery',
'jquery.flot': '../vendor/jquery/jquery.flot',
'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie',
'jquery.flot.events': '../vendor/jquery/jquery.flot.events',
'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection',
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
modernizr: '../vendor/modernizr-2.6.1',
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
},
shim: {
spectrum: {
deps: ['jquery']
},
crypto: {
exports: 'Crypto'
},
angular: {
deps: ['jquery','config'],
exports: 'angular'
},
bootstrap: {
deps: ['jquery']
},
modernizr: {
exports: 'Modernizr'
},
jquery: {
exports: 'jQuery'
},
// simple dependency declaration
//
'jquery.flot': ['jquery'],
'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'],
'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'],
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
'angular-dragdrop': ['jquery', 'angular'],
'angular-mocks': ['angular'],
'angular-sanitize': ['angular'],
'angular-route': ['angular'],
'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'],
'bindonce': ['angular'],
timepicker: ['jquery', 'bootstrap'],
datepicker: ['jquery', 'bootstrap'],
'bootstrap-tagsinput': ['jquery'],
},
waitSeconds: 60,
});

View File

@ -1,9 +1,6 @@
/**
* Bootstrap require with the needed config, then load the app.js module.
*/
require.config({
baseUrl: 'app',
urlArgs: 'bust=' + (new Date().getTime()),
baseUrl: 'app',
paths: {
config: ['../config', '../config.sample'],
@ -47,7 +44,6 @@ require.config({
modernizr: '../vendor/modernizr-2.6.1',
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
},
shim: {

View File

@ -16,24 +16,22 @@ function (_, crypto) {
datasources : {},
window_title_prefix : 'Grafana - ',
panels : {
'graph': { path: 'panels/graph' },
'singlestat': { path: 'panels/singlestat' },
'text': { path: 'panels/text' }
'graph': { path: 'panels/graph', name: 'Graph' },
'singlestat': { path: 'panels/singlestat', name: 'Single stat' },
'text': { path: 'panels/text', name: 'Text' },
'dashlist': { path: 'panels/dashlist', name: 'Dashboard list' },
},
plugins : {},
default_route : '/dashboard/file/default.json',
playlist_timespan : "1m",
unsaved_changes_warning : true,
search : { max_results: 100 },
admin : {}
new_panel_title: 'no title (click here)',
plugins: {},
default_route: '/dashboard/file/default.json',
playlist_timespan: "1m",
unsaved_changes_warning: true,
search: { max_results: 100 },
admin: {},
appSubUrl: ""
};
// This initializes a new hash on purpose, to avoid adding parameters to
// config.js without providing sane defaults
var settings = {};
_.each(defaults, function(value, key) {
settings[key] = typeof options[key] !== 'undefined' ? options[key] : defaults[key];
});
var settings = _.extend({}, defaults, options);
var parseBasicAuth = function(datasource) {
var passwordEnd = datasource.url.indexOf('@');

View File

@ -8,9 +8,15 @@ define([], function() {
set: function(key, value) {
window.localStorage[key] = value;
},
getBool: function(key) {
getBool: function(key, def) {
if (def !== void 0 && !this.exists(key)) {
return def;
}
return window.localStorage[key] === 'true' ? true : false;
},
exists: function(key) {
return window.localStorage[key] !== void 0;
},
delete: function(key) {
window.localStorage.removeItem(key);
}

View File

@ -6,4 +6,7 @@ define([
'./graphiteImport',
'./inspectCtrl',
'./jsonEditorCtrl',
'./loginCtrl',
'./sidemenuCtrl',
'./errorCtrl',
], function () {});

View File

@ -0,0 +1,22 @@
define([
'angular',
'app',
'lodash'
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('ErrorCtrl', function($scope) {
var showSideMenu = $scope.grafana.sidemenu;
$scope.grafana.sidemenu = false;
$scope.$on('$destroy', function() {
$scope.grafana.sidemenu = showSideMenu;
});
});
});

View File

@ -10,32 +10,47 @@ function (angular, config, _, $, store) {
var module = angular.module('grafana.controllers');
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
$scope._ = _;
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
$rootScope.performance = { loadStart: new Date().getTime() };
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller, userSrv, $timeout) {
$scope.init = function() {
$scope.grafana = {};
$scope.grafana.version = grafanaVersion;
$scope._ = _;
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
$rootScope.performance = { loadStart: new Date().getTime() };
$rootScope.appSubUrl = config.appSubUrl;
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
alertSrv.init();
utilSrv.init();
$scope.dashAlerts = alertSrv;
$scope.grafana = { style: 'dark' };
};
$scope.grafana.lightTheme = false;
$scope.grafana.user = userSrv.getSignedInUser();
$scope.grafana.sidemenu = store.getBool('grafana.sidemenu');
$scope.topnav = { title: 'Grafana' };
$scope.toggleConsole = function() {
$scope.consoleEnabled = !$scope.consoleEnabled;
store.set('grafanaConsole', $scope.consoleEnabled);
$scope.onAppEvent('logged-out', function() {
$scope.grafana.sidemenu = false;
$scope.grafana.user = {};
});
};
$scope.initDashboard = function(dashboardData, viewScope) {
$controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
};
$scope.toggleSideMenu = function() {
$scope.grafana.sidemenu = !$scope.grafana.sidemenu;
store.set('grafana.sidemenu', $scope.grafana.sidemenu);
$timeout(function() {
$scope.$broadcast("render");
}, 50);
};
$rootScope.onAppEvent = function(name, callback) {
var unbind = $rootScope.$on(name, callback);
this.$on('$destroy', unbind);
@ -81,7 +96,11 @@ function (angular, config, _, $, store) {
$scope.initProfiling = function() {
var count = 0;
$scope.$watch(function digestCounter() { count++; }, function() { });
$scope.$watch(function digestCounter() {
count++;
}, function() {
});
$scope.onAppEvent('dashboard-loaded', function() {
count = 0;

View File

@ -0,0 +1,103 @@
define([
'angular',
'config',
],
function (angular, config) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('LoginCtrl', function($scope, backendSrv) {
$scope.formModel = {
user: '',
email: '',
password: '',
};
$scope.grafana.sidemenu = false;
$scope.googleAuthEnabled = config.googleAuthEnabled;
$scope.githubAuthEnabled = config.githubAuthEnabled;
$scope.disableUserSignUp = config.disableUserSignUp;
$scope.loginMode = true;
$scope.submitBtnClass = 'btn-inverse';
$scope.submitBtnText = 'Log in';
$scope.strengthClass = '';
$scope.init = function() {
$scope.$watch("loginMode", $scope.loginModeChanged);
$scope.passwordChanged();
};
// build info view model
$scope.buildInfo = {
version: config.buildInfo.version,
commit: config.buildInfo.commit,
buildstamp: new Date(config.buildInfo.buildstamp * 1000)
};
$scope.submit = function() {
if ($scope.loginMode) {
$scope.login();
} else {
$scope.signUp();
}
};
$scope.loginModeChanged = function(newValue) {
$scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
};
$scope.passwordChanged = function(newValue) {
if (!newValue) {
$scope.strengthText = "";
$scope.strengthClass = "hidden";
return;
}
if (newValue.length < 4) {
$scope.strengthText = "strength: weak sauce.";
$scope.strengthClass = "password-strength-bad";
return;
}
if (newValue.length <= 6) {
$scope.strengthText = "strength: you can do better.";
$scope.strengthClass = "password-strength-ok";
return;
}
$scope.strengthText = "strength: strong like a bull.";
$scope.strengthClass = "password-strength-good";
};
$scope.signUp = function() {
if (!$scope.loginForm.$valid) {
return;
}
backendSrv.post('/api/user/signup', $scope.formModel).then(function() {
window.location.href = config.appSubUrl + '/';
});
};
$scope.login = function() {
delete $scope.loginError;
if (!$scope.loginForm.$valid) {
return;
}
backendSrv.post('/login', $scope.formModel).then(function(result) {
if (result.redirectUrl) {
window.location.href = result.redirectUrl;
} else {
window.location.href = config.appSubUrl + '/';
}
});
};
$scope.init();
});
});

View File

@ -64,33 +64,18 @@ function (angular, _, config, $) {
$scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
};
$scope.goToDashboard = function(id) {
$scope.goToDashboard = function(slug) {
$location.search({});
$location.path("/dashboard/db/" + id);
};
$scope.shareDashboard = function(title, id, $event) {
$event.stopPropagation();
var baseUrl = window.location.href.replace(window.location.hash,'');
$scope.share = {
title: title,
url: baseUrl + '#dashboard/db/' + encodeURIComponent(id)
};
$location.path("/dashboard/db/" + slug);
};
$scope.searchDashboards = function(queryString) {
// bookeeping for determining stale search requests
var searchId = $scope.currentSearchId + 1;
$scope.currentSearchId = searchId > $scope.currentSearchId ? searchId : $scope.currentSearchId;
$scope.currentSearchId = $scope.currentSearchId + 1;
var localSearchId = $scope.currentSearchId;
return $scope.db.searchDashboards(queryString)
.then(function(results) {
// since searches are async, it's possible that these results are not for the latest search. throw
// them away if so
if (searchId < $scope.currentSearchId) {
return;
}
if (localSearchId < $scope.currentSearchId) { return; }
$scope.tagsOnly = results.tagsOnly;
$scope.results.dashboards = results.dashboards;
@ -125,11 +110,11 @@ function (angular, _, config, $) {
$scope.deleteDashboard = function(dash, evt) {
evt.stopPropagation();
$scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
$scope.appEvent('delete-dashboard', { slug: dash.slug, title: dash.title });
};
$scope.dashboardDeleted = function(evt, id) {
var dash = _.findWhere($scope.results.dashboards, {id: id});
$scope.dashboardDeleted = function(evt, payload) {
var dash = _.findWhere($scope.results.dashboards, { slug: payload.slug });
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
};
@ -154,7 +139,7 @@ function (angular, _, config, $) {
};
$scope.newDashboard = function() {
$location.url('/dashboard/file/empty.json');
$location.url('dashboard/new');
};
});

View File

@ -0,0 +1,107 @@
define([
'angular',
'lodash',
'jquery',
'config',
],
function (angular, _, $, config) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('SideMenuCtrl', function($scope, $location) {
$scope.getUrl = function(url) {
return config.appSubUrl + url;
};
$scope.menu = [];
$scope.menu.push({
text: "Dashbords",
icon: "fa fa-th-large",
href: $scope.getUrl("/"),
//startsWith: config.appSubUrl + '/dashboard/',
});
if ($scope.grafana.user.accountRole === 'Admin') {
$scope.menu.push({
text: "Data Sources",
icon: "fa fa-database",
href: $scope.getUrl("/account/datasources"),
});
$scope.menu.push({
text: "Account", href: $scope.getUrl("/account"),
requireRole: "Admin",
icon: "fa fa-shield",
});
$scope.menu.push({
text: "Users", href: $scope.getUrl("/account/users"),
requireRole: "Admin",
icon: "fa fa-users",
});
$scope.menu.push({
text: "API Keys", href: $scope.getUrl("/account/apikeys"),
requireRole: "Admin",
icon: "fa fa-key",
});
}
if ($scope.grafana.user.isGrafanaAdmin) {
$scope.menu.push({
text: "Admin", href: $scope.getUrl("/admin/users"),
icon: "fa fa-cube",
requireSignedIn: true,
links: [
{ text: 'Settings', href: $scope.getUrl("/admin/settings")},
{ text: 'Users', href: $scope.getUrl("/admin/users"), icon: "fa fa-lock" },
{ text: 'Log', href: "", icon: "fa fa-lock" },
]
});
}
$scope.updateState = function() {
var currentPath = config.appSubUrl + $location.path();
var search = $location.search();
_.each($scope.menu, function(item) {
item.active = false;
if (item.href === currentPath) {
item.active = true;
}
if (item.startsWith) {
if (currentPath.indexOf(item.startsWith) === 0) {
item.active = true;
item.href = currentPath;
}
}
_.each(item.links, function(link) {
link.active = false;
if (link.editview) {
var params = {};
_.each(search, function(value, key) {
if (value !== null) { params[key] = value; }
});
params.editview = link.editview;
link.href = currentPath + '?' + $.param(params);
}
if (link.href === currentPath) {
item.active = true;
link.active = true;
}
});
});
};
$scope.init = function() {
$scope.updateState();
};
});
});

View File

@ -1,7 +1,6 @@
define([
'./arrayJoin',
'./dashUpload',
'./grafanaPanel',
'./grafanaSimplePanel',
'./ngBlur',
'./dashEditLink',
@ -16,4 +15,5 @@ define([
'./graphiteSegment',
'./grafanaVersionCheck',
'./dropdown.typeahead',
'./topnav',
], function () {});

View File

@ -5,6 +5,12 @@ define([
function (angular, $) {
'use strict';
var editViewMap = {
'settings': { src: 'app/partials/dasheditor.html', title: "Settings" },
'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" },
'templating': { src: 'app/partials/templating_editor.html', title: "Templating" }
};
angular
.module('grafana.directives')
.directive('dashEditorLink', function($timeout) {
@ -25,7 +31,7 @@ function (angular, $) {
angular
.module('grafana.directives')
.directive('dashEditorView', function($compile) {
.directive('dashEditorView', function($compile, $location) {
return {
restrict: 'A',
link: function(scope, elem) {
@ -48,10 +54,12 @@ function (angular, $) {
if (editorScope) { editorScope.dismiss(); }
}
scope.$on("$destroy", hideEditorPane);
scope.onAppEvent('hide-dash-editor', hideEditorPane);
function showEditorPane(evt, payload, editview) {
if (editview) {
scope.grafana.editview = editViewMap[editview];
payload.src = scope.grafana.editview.src;
}
scope.onAppEvent('show-dash-editor', function(evt, payload) {
if (lastEditor === payload.src) {
hideEditorPane();
return;
@ -65,23 +73,43 @@ function (angular, $) {
editorScope = payload.scope ? payload.scope.$new() : scope.$new();
editorScope.dismiss = function() {
console.log('dismiss: ');
editorScope.$destroy();
elem.empty();
lastEditor = null;
editorScope = null;
hideScrollbars(false);
if (editview) {
var urlParams = $location.search();
if (editview === urlParams.editview) {
delete urlParams.editview;
$location.search(urlParams);
}
}
};
// hide page scrollbars while edit pane is visible
hideScrollbars(true);
var src = "'" + payload.src + "'";
var view = $('<div class="dashboard-edit-view" ng-include="' + src + '"></div>');
var view = $('<div class="gf-box" ng-include="' + src + '"></div>');
elem.append(view);
$compile(elem.contents())(editorScope);
}
scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
if (newValue) {
showEditorPane(null, {}, newValue);
} else if (oldValue) {
scope.grafana.editview = null;
hideEditorPane();
}
});
scope.grafana.editview = null;
scope.$on("$destroy", hideEditorPane);
scope.onAppEvent('hide-dash-editor', hideEditorPane);
scope.onAppEvent('show-dash-editor', showEditorPane);
}
};
});

View File

@ -1,89 +0,0 @@
define([
'angular',
'jquery',
'config',
'./panelMenu',
],
function (angular, $, config) {
'use strict';
angular
.module('grafana.directives')
.directive('grafanaPanel', function($compile, $parse) {
var container = '<div class="panel-container"></div>';
var content = '<div class="panel-content"></div>';
var panelHeader =
'<div class="panel-header">'+
'<span class="alert-error panel-error small pointer"' +
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
'<span data-placement="top" bs-tooltip="panelMeta.error">' +
'<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>' +
'</span>' +
'</span>' +
'<span class="panel-loading" ng-show="panelMeta.loading">' +
'<i class="fa fa-spinner fa-spin"></i>' +
'</span>' +
'<div class="panel-title-container drag-handle" panel-menu></div>' +
'</div>'+
'</div>';
return {
restrict: 'E',
link: function($scope, elem, attr) {
var getter = $parse(attr.type), panelType = getter($scope);
var newScope = $scope.$new();
$scope.kbnJqUiDraggableOptions = {
revert: 'invalid',
helper: function() {
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
},
placeholder: 'keep'
};
// compile the module and uncloack. We're done
function loadModule($module) {
$module.appendTo(elem);
elem.wrap(container);
/* jshint indent:false */
$compile(elem.contents())(newScope);
elem.removeClass("ng-cloak");
var panelCtrlElem = $(elem.children()[0]);
var panelCtrlScope = panelCtrlElem.data().$scope;
panelCtrlScope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
panelCtrlElem.css({ minHeight: panelCtrlScope.panel.height || panelCtrlScope.row.height });
panelCtrlElem.toggleClass('panel-fullscreen', panelCtrlScope.fullscreen ? true : false);
});
}
newScope.$on('$destroy',function() {
elem.unbind();
elem.remove();
});
elem.addClass('ng-cloak');
var panelPath = config.panels[panelType].path;
$scope.require([
'jquery',
'text!'+panelPath+'/module.html',
panelPath + "/module",
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
$module.prepend(panelHeader);
$module.first().find('.panel-header').nextAll().wrapAll(content);
loadModule($module);
});
}
};
});
});

View File

@ -18,6 +18,21 @@ function (angular, kbn) {
};
});
angular
.module('grafana.directives')
.directive('watchChange', function() {
return {
scope: { onchange: '&watchChange' },
link: function(scope, element) {
element.on('input', function() {
scope.$apply(function () {
scope.onchange({ inputValue: element.val() });
});
});
}
};
});
angular
.module('grafana.directives')
.directive('editorOptBool', function($compile) {

View File

@ -0,0 +1,48 @@
define([
'angular',
'kbn'
],
function (angular) {
'use strict';
angular
.module('grafana.directives')
.directive('topnav', function() {
return {
restrict: 'E',
transclude: true,
scope: {
title: "@",
section: "@",
titleAction: "&",
toggle: "&",
showMenuBtn: "=",
},
template:
'<div class="navbar navbar-static-top"><div class="navbar-inner"><div class="container-fluid">' +
'<div class="top-nav">' +
'<a class="top-nav-menu-btn pointer" ng-if="showMenuBtn" ng-click="toggle()">' +
'<img class="logo-icon" src="img/fav32.png"></img> ' +
'<i class="fa fa-angle-right"></i>' +
'</a>' +
'<span class="top-nav-breadcrumb">' +
'<i class="top-nav-icon" ng-class="icon"></i>' +
'</span>' +
'<span class="top-nav-section" ng-show="section">' +
'{{section}}' +
'<i class="fa fa-angle-right"></i>' +
'</span>' +
'<a ng-click="titleAction()" class="top-nav-title">' +
'{{title}}' +
'</a>' +
'</div><div ng-transclude></div></div></div></div>',
link: function(scope, elem, attrs) {
scope.icon = attrs.icon;
}
};
});
});

View File

@ -0,0 +1,29 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AccountCtrl', function($scope, $http, backendSrv) {
$scope.init = function() {
$scope.getAccount();
};
$scope.getAccount = function() {
backendSrv.get('/api/account').then(function(account) {
$scope.account = account;
});
};
$scope.update = function() {
if (!$scope.accountForm.$valid) { return; }
backendSrv.put('/api/account', $scope.account).then($scope.getAccount);
};
$scope.init();
});
});

View File

@ -0,0 +1,38 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AccountUsersCtrl', function($scope, $http, backendSrv) {
$scope.user = {
loginOrEmail: '',
role: 'Viewer',
};
$scope.init = function() {
$scope.get();
};
$scope.get = function() {
backendSrv.get('/api/account/users').then(function(users) {
$scope.users = users;
});
};
$scope.removeUser = function(user) {
backendSrv.delete('/api/account/users/' + user.userId).then($scope.get);
};
$scope.addUser = function() {
if (!$scope.form.$valid) { return; }
backendSrv.post('/api/account/users', $scope.user).then($scope.get);
};
$scope.init();
});
});

View File

@ -0,0 +1,35 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('ApiKeysCtrl', function($scope, $http, backendSrv) {
$scope.roleTypes = ['Viewer', 'Editor', 'Admin'];
$scope.token = { role: 'Viewer' };
$scope.init = function() {
$scope.getTokens();
};
$scope.getTokens = function() {
backendSrv.get('/api/auth/keys').then(function(tokens) {
$scope.tokens = tokens;
});
};
$scope.removeToken = function(id) {
backendSrv.delete('/api/auth/keys/'+id).then($scope.getTokens);
};
$scope.addToken = function() {
backendSrv.post('/api/auth/keys', $scope.token).then($scope.getTokens);
};
$scope.init();
});
});

View File

@ -0,0 +1,88 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('DataSourcesCtrl', function($scope, $http, backendSrv) {
var defaults = {
name: '',
type: 'graphite',
url: '',
access: 'proxy'
};
$scope.types = [
{ name: 'Graphite', type: 'graphite' },
{ name: 'InfluxDB', type: 'influxdb' },
{ name: 'Elasticsearch', type: 'elasticsearch' },
{ name: 'OpenTSDB', type: 'opentsdb' },
];
$scope.init = function() {
$scope.reset();
$scope.editor = {index: 0};
$scope.datasources = [];
$scope.getDatasources();
$scope.$watch('editor.index', function(newVal) {
if (newVal !== 2) {
$scope.reset();
}
});
};
$scope.reset = function() {
$scope.current = angular.copy(defaults);
$scope.currentIsNew = true;
};
$scope.edit = function(ds) {
$scope.current = ds;
$scope.currentIsNew = false;
$scope.editor.index = 2;
};
$scope.cancel = function() {
$scope.reset();
$scope.editor.index = 0;
};
$scope.getDatasources = function() {
backendSrv.get('/api/datasources').then(function(results) {
$scope.datasources = results;
});
};
$scope.remove = function(ds) {
backendSrv.delete('/api/datasources/' + ds.id).then(function() {
$scope.getDatasources();
});
};
$scope.update = function() {
backendSrv.post('/api/datasources', $scope.current).then(function() {
$scope.editor.index = 0;
$scope.getDatasources();
});
};
$scope.add = function() {
if (!$scope.editForm.$valid) {
return;
}
backendSrv.put('/api/datasources', $scope.current)
.then(function() {
$scope.editor.index = 0;
$scope.getDatasources();
});
};
$scope.init();
});
});

View File

@ -0,0 +1,78 @@
define([
'angular',
'lodash',
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('ImportCtrl', function($scope, $http, backendSrv, datasourceSrv) {
$scope.init = function() {
$scope.datasources = [];
$scope.sourceName = 'grafana';
$scope.destName = 'grafana';
$scope.imported = [];
$scope.dashboards = [];
$scope.infoText = '';
$scope.importing = false;
_.each(datasourceSrv.getAll(), function(ds) {
if (ds.type === 'influxdb' || ds.type === 'elasticsearch') {
$scope.sourceName = ds.name;
$scope.datasources.push(ds.name);
} else if (ds.type === 'grafana') {
$scope.datasources.push(ds.name);
}
});
};
$scope.startImport = function() {
$scope.sourceDs = datasourceSrv.get($scope.sourceName);
$scope.destDs = datasourceSrv.get($scope.destName);
$scope.sourceDs.searchDashboards('title:').then(function(results) {
$scope.dashboards = results.dashboards;
if ($scope.dashboards.length === 0) {
$scope.infoText = 'No dashboards found';
return;
}
$scope.importing = true;
$scope.imported = [];
$scope.next();
});
};
$scope.next = function() {
if ($scope.dashboards.length === 0) {
$scope.infoText = "Done! Imported " + $scope.imported.length + " dashboards";
}
var dash = $scope.dashboards.shift();
if (!dash.title) {
console.log(dash);
return;
}
var infoObj = {name: dash.title, info: 'Importing...'};
$scope.imported.push(infoObj);
$scope.infoText = "Importing " + $scope.imported.length + '/' + ($scope.imported.length + $scope.dashboards.length);
$scope.sourceDs.getDashboard(dash.id).then(function(loadedDash) {
$scope.destDs.saveDashboard(loadedDash).then(function() {
infoObj.info = "Done!";
$scope.next();
}, function(err) {
infoObj.info = "Error: " + err;
$scope.next();
});
});
};
$scope.init();
});
});

View File

@ -0,0 +1,35 @@
<topnav toggle="toggleSideMenu()" icon="fa fa-shield" section="Account" show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-body">
<div class="row editor-row">
<div class="section">
<form name="accountForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 120px">
<strong>Account name</strong>
</li>
<li>
<input type="text" required ng-model="account.name" class="input-xlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<br>
<panel-loader type="'test'"></panel-loader>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,55 @@
<topnav toggle="toggleSideMenu()"
title="API Keys"
icon="fa fa-shield"
section="Account"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-body">
<div class="editor-row">
<div class="section">
<form name="addTokenrForm" class="form-inline tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
Add a Token
</li>
<li>
<input type="text" class="input-xlarge tight-form-input" ng-model='token.name' placeholder="Name"></input>
</li>
<li class="tight-form-item">
Role
</li>
<li>
<select class="input-small tight-form-input" ng-model="token.role" ng-options="r for r in roleTypes"></select>
</li>
<button class="btn btn-success tight-form-btn" ng-click="addToken()">Add</button>
</li>
</ul>
<div class="clearfix"></div>
</form>
</div>
<div class="editor-row row">
<div class="section span6">
<table class="grafana-options-table">
<tr ng-repeat="t in tokens">
<td>{{t.name}}</td>
<td>{{t.role}}</td>
<td>{{t.key}}</td>
<td style="width: 1%">
<a ng-click="removeToken(t.id)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,119 @@
<topnav toggle="toggleSideMenu()"
title="Data sources"
icon="fa fa-shield"
section="Account"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-header">
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Overview', 'Add', 'Edit']" data-title="{{tab}}">
</div>
</div>
</div>
<form name="editForm">
<div class="gf-box-body">
<div class="editor-row row" ng-if="editor.index == 0">
<div class="span8">
<div ng-if="datasources.length === 0">
<em>No datasources defined</em>
</div>
<table class="grafana-options-table" ng-if="datasources.length > 0">
<tr>
<td><strong>Name</strong></td>
<td><strong>Url</strong></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr ng-repeat="ds in datasources">
<td style="width:1%">
<i class="fa fa-database"></i> &nbsp;
{{ds.name}}
</td>
<td style="width:90%">
{{ds.url}}
</td>
<td style="width:2%" class="text-center">
<span ng-if="ds.isDefault">
<span class="label label-info">default</span>
</span>
</td>
<td style="width: 1%">
<a ng-click="edit(ds)" class="btn btn-success btn-mini">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
<td style="width: 1%">
<a ng-click="remove(ds)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
<div class="editor-row">
<div class="editor-option">
<label class="small">Data source name</label>
<input type="text" class="input-large" ng-model='current.name' placeholder="production" required></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select class="input-medium" ng-model="current.type" ng-options="f.type as f.name for f in types" ng-change="typeChanged()"></select>
</div>
<editor-opt-bool text="Mark as default" model="current.isDefault" change="render()"></editor-opt-bool>
</div>
<div class="editor-row">
<div class="editor-option">
<label class="small">Url</label>
<input type="text" class="input-xxlarge" ng-model='current.url' placeholder="http://my.graphite.com:8080" required></input>
</div>
<div class="editor-option">
<label class="small">Access method <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</label>
<select class="input-medium" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
</div>
</div>
<div class="editor-row" ng-if="current.type === 'influxdb'">
<div class="section">
<h5>InfluxDB Details</h5>
<div class="editor-option">
<label class="small">Database name</label>
<input type="text" class="input-large" required ng-model='current.database' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">User</label>
<input type="text" class="input-large" ng-model='current.user' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">Password</label>
<input type="password" class="input-large" ng-model='current.password' placeholder=""></input>
</div>
</div>
</div>
<div class="editor-row" ng-if="current.type === 'elasticsearch'">
<div class="section">
<h5>Elastic search details</h5>
<div class="editor-option">
<label class="small">Index name</label>
<input type="text" class="input-large" required ng-model='current.database' placeholder=""></input>
</div>
</div>
</div>
</div>
<br>
<button type="submit" class="btn btn-success" ng-show="editor.index === 1" ng-click="add()">Add</button>
<button type="submit" class="btn btn-success" ng-show="editor.index === 2 && !currentIsNew" ng-click="update()">Update</button>
<button type="submit" class="btn btn-inverse" ng-show="editor.index === 2 && !currentIsNew" ng-click="cancel()">Cancel</button>
<br>
</form>
</div>
</div>

View File

@ -0,0 +1,63 @@
<topnav toggle="toggleSideMenu()"
title="Import"
icon="fa fa-download"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-th-large"></i>
Import Dashboards
</div>
</div>
<div class="gf-box-body">
<div class="editor-row">
<div class="section">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
<strong>Dashboard source</strong>
</li>
<li>
<select type="text" ng-model="sourceName" class="input-small tight-form-input" ng-options="f for f in datasources">
</select>
</li>
<li class="tight-form-item" style="width: 160px">
<strong>Destination</strong>
</li>
<li>
<select type="text" ng-model="destName" class="input-small tight-form-input" ng-options="f for f in datasources">
</select>
</li>
<li>
<button class="btn btn-success tight-form-btn" ng-click="startImport()">Import</button>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="editor-row" ng-if="importing">
<section class="section">
<h5>{{infoText}}</h5>
<div class="editor-row row">
<table class="grafana-options-table span5">
<tr ng-repeat="dash in imported">
<td>{{dash.name}}</td>
<td>
{{dash.info}}
</td>
</tr>
</table>
</div>
</section>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
<topnav toggle="toggleSideMenu()" title="Users" icon="fa fa-shield" section="Account" show-menu-btn="!grafana.sidemenu"></topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-body">
<div class="editor-row">
<div class="section">
<form name="form">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
<strong>Username or Email</strong>
</li>
<li>
<input type="text" ng-model="user.loginOrEmail" required class="input-xlarge tight-form-input" placeholder="user@email.com or username">
</li>
<li class="tight-form-item">
role
</li>
<li>
<select type="text" ng-model="user.role" class="input-small tight-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']">
</select>
</li>
<li>
<button class="btn btn-success tight-form-btn" ng-click="addUser()">Add</button>
</li>
</ul>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
<div class="editor-row row">
<table class="grafana-options-table span5">
<tr ng-repeat="user in users">
<td>{{user.email}}</td>
<td>
{{user.role}}
</td>
<td style="width: 1%">
<a ng-click="removeUser(user)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AdminCtrl', function($scope) {
$scope.init = function() {
$scope.editor = {index: 0};
};
$scope.init();
});
});

View File

@ -0,0 +1,18 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AdminSettingsCtrl', function($scope) {
$scope.init = function() {
};
$scope.init();
});
});

View File

@ -0,0 +1,25 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AdminUsersCtrl', function($scope, backendSrv) {
$scope.init = function() {
$scope.accounts = [];
$scope.getUsers();
};
$scope.getUsers = function() {
backendSrv.get('/api/admin/users').then(function(users) {
$scope.users = users;
});
};
$scope.init();
});
});

View File

@ -0,0 +1,9 @@
<topnav toggle="toggleSideMenu()"
title="Users"
icon="fa fa-cube"
section="Admin"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
</div>

View File

@ -0,0 +1,11 @@
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Admin > Settings'"></div>
<div class="dashboard-edit-view" style="min-height: 500px">
<div class="dashboard-editor-body">
<div class="editor-row row">
<div class="section span6">
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
<topnav toggle="toggleSideMenu()"
title="Users"
icon="fa fa-cube"
section="Admin"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="gf-box" style="min-height: 500px">
<div class="gf-box-body">
<div class="editor-row row">
<div class="section span6">
<table class="grafana-options-table">
<tr>
<th style="text-align:left">Id</th>
<th>Login</th>
<th>Email</th>
<th>Name</th>
<th>Admin</th>
<th></th>
</tr>
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td>{{user.login}}</td>
<td>{{user.email}}</td>
<td>{{user.name}}</td>
<td>{{user.isAdmin}}</td>
<td style="width: 1%">
<a ng-click="edit(variable)" class="btn btn-success btn-small">
<i class="fa fa-edit"></i>
Edit
</a>
&nbsp;&nbsp;
<a ng-click="edit(variable)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>

View File

@ -7,4 +7,14 @@ define([
'./opentsdb/datasource',
'./elasticsearch/datasource',
'./dashboard/all',
'./panel/all',
'./profile/profileCtrl',
'./account/accountUsersCtrl',
'./account/datasourcesCtrl',
'./account/apiKeysCtrl',
'./account/importCtrl',
'./account/accountCtrl',
'./admin/adminUsersCtrl',
'./admin/adminSettingsCtrl',
'./grafanaDatasource/datasource',
], function () {});

View File

@ -1,7 +1,7 @@
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-bolt"></i>
Annotations
</div>
@ -10,10 +10,12 @@
<div ng-repeat="tab in ['Overview', 'Add', 'Edit']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div class="editor-row row" ng-if="editor.index == 0">
<div class="span6">
<div ng-if="annotations.length === 0">
@ -71,12 +73,12 @@
<div ng-include src="currentDatasource.editorSrc">
</div>
<br>
<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>
<button ng-show="editor.index === 2" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
<br>
<br>
</div>
</div>
<div class="dashboard-editor-footer">
<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>
<button ng-show="editor.index === 2" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
<button type="button" class="btn btn-success pull-right" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
</div>
</div>

View File

@ -9,7 +9,6 @@ define([
'./keybindings',
'./viewStateSrv',
'./playlistSrv',
'./panelSrv',
'./timeSrv',
'./unsavedChangesSrv',
], function () {});

View File

@ -4,7 +4,7 @@ define([
'config',
'lodash',
],
function (angular, $, config, _) {
function (angular, $, config) {
"use strict";
var module = angular.module('grafana.controllers');
@ -20,15 +20,15 @@ function (angular, $, config, _) {
$timeout) {
$scope.editor = { index: 0 };
$scope.panelNames = _.map(config.panels, function(value, key) { return key; });
$scope.panels = config.panels;
var resizeEventTimeout;
this.init = function(dashboardData) {
$scope.availablePanels = config.panels;
this.init = function(dashboard) {
$scope.reset_row();
$scope.registerWindowResizeEvent();
$scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
$scope.setupDashboard(dashboardData);
$scope.setupDashboard(dashboard);
};
$scope.registerWindowResizeEvent = function() {
@ -38,13 +38,14 @@ function (angular, $, config, _) {
});
};
$scope.setupDashboard = function(dashboardData) {
$scope.setupDashboard = function(dashboard) {
$rootScope.performance.dashboardLoadStart = new Date().getTime();
$rootScope.performance.panelsInitialized = 0;
$rootScope.performance.panelsRendered = 0;
$scope.dashboard = dashboardSrv.create(dashboardData);
$scope.dashboard = dashboardSrv.create(dashboard.model);
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
$scope.dashboardMeta = dashboard.meta;
// init services
timeSrv.init($scope.dashboard);
@ -60,15 +61,11 @@ function (angular, $, config, _) {
$scope.setWindowTitleAndTheme = function() {
window.document.title = config.window_title_prefix + $scope.dashboard.title;
$scope.grafana.style = $scope.dashboard.style;
$scope.grafana.lightTheme = $scope.dashboard.style === 'light';
};
$scope.isPanel = function(obj) {
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
return true;
} else {
return false;
}
$scope.styleUpdated = function() {
$scope.grafana.lightTheme = $scope.dashboard.style === 'light';
};
$scope.add_row = function(dash, row) {

View File

@ -22,7 +22,6 @@ function (angular, _, moment, config, store) {
$scope.onAppEvent('zoom-out', function() {
$scope.zoom(2);
});
};
$scope.set_default = function() {
@ -35,17 +34,29 @@ function (angular, _, moment, config, store) {
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000);
};
$scope.saveForSharing = function() {
var clone = angular.copy($scope.dashboard);
clone.temp = true;
$scope.db.saveDashboard(clone)
.then(function(result) {
$scope.openEditView = function(editview) {
var search = _.extend($location.search(), {editview: editview});
$location.search(search);
};
$scope.share = { url: result.url, title: result.title };
}, function(err) {
alertSrv.set('Save for sharing failed', err, 'error',5000);
$scope.starDashboard = function() {
if ($scope.dashboardMeta.isStarred) {
$scope.db.unstarDashboard($scope.dashboard.id).then(function() {
$scope.dashboardMeta.isStarred = false;
});
}
else {
$scope.db.starDashboard($scope.dashboard.id).then(function() {
$scope.dashboardMeta.isStarred = true;
});
}
};
$scope.shareDashboard = function() {
$scope.appEvent('show-modal', {
src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new(),
});
};
$scope.passwordCache = function(pwd) {
@ -105,10 +116,10 @@ function (angular, _, moment, config, store) {
};
$scope.deleteDashboardConfirmed = function(options) {
var id = options.id;
$scope.db.deleteDashboard(id).then(function(id) {
$scope.appEvent('dashboard-deleted', id);
$scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
var slug = options.slug;
$scope.db.deleteDashboard(slug).then(function() {
$scope.appEvent('dashboard-deleted', options);
$scope.appEvent('alert-success', ['Dashboard Deleted', options.title + ' has been deleted']);
}, function(err) {
$scope.appEvent('alert-error', ['Deleted failed', err]);
});
@ -140,10 +151,6 @@ function (angular, _, moment, config, store) {
});
};
$scope.styleUpdated = function() {
$scope.grafana.style = $scope.dashboard.style;
};
$scope.editJson = function() {
$scope.appEvent('show-json-editor', { object: $scope.dashboard });
};

View File

@ -0,0 +1,35 @@
<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-share"></i>
Share
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="gf-box-body">
<div class="editor-row">
<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
</div>
<div class="editor-row" style="margin-top: 20px;">
<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
</div>
<div class="editor-row" style="margin-top: 20px;">
<a href="{{imageUrl}}" target="_blank">Link to rendered image</a>
</div>
</div>
</div>

View File

@ -1,9 +1,10 @@
define([
'angular',
'app',
'lodash'
'lodash',
'config'
],
function (angular, app, _) {
function (angular, app, _, config) {
'use strict';
var module = angular.module('grafana.controllers');
@ -107,11 +108,11 @@ function (angular, app, _) {
var _as = 12 - $scope.dashboard.rowSpan($scope.row);
$scope.panel = {
title: 'no title (click here)',
error : false,
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
title: config.new_panel_title,
error: false,
span: _as < defaultSpan && _as > 0 ? _as : defaultSpan,
editable: true,
type : type
type: type
};
function fixRowHeight(height) {

View File

@ -12,9 +12,12 @@ function (angular, _) {
$scope.init = function() {
$scope.editor = { index: 0 };
$scope.forCurrent = true;
$scope.toPanel = true;
$scope.includeTemplateVars = true;
if ($scope.panel) {
$scope.toPanel = true;
}
$scope.includeTemplateVars = true;
$scope.buildUrl();
};
@ -26,7 +29,6 @@ function (angular, _) {
baseUrl = baseUrl.substring(0, queryStart);
}
var panelId = $scope.panel.id;
var params = angular.copy($location.search());
var range = timeSrv.timeRangeForUrl();
@ -50,7 +52,7 @@ function (angular, _) {
}
if ($scope.toPanel) {
params.panelId = panelId;
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
delete params.panelId;
@ -68,7 +70,10 @@ function (angular, _) {
}
});
$scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ;
$scope.shareUrl = baseUrl + "?" + paramsArray.join('&');
$scope.imageUrl = $scope.shareUrl.replace('/dashboard/db/', '/render/dashboard/solo/');
$scope.imageUrl += '&width=1000';
$scope.imageUrl += '&height=500';
$timeout(function() {
var input = $element.find('[data-share-panel-url]');

View File

@ -60,6 +60,7 @@ function(angular, _, config) {
this.open_modal = function() {
var confirmModal = $modal({
template: './app/partials/unsaved-changes.html',
modalClass: 'confirm-modal',
persist: true,
show: false,
scope: modalScope,

View File

@ -53,12 +53,13 @@ function (angular, _, $) {
state.panelId = parseInt(state.panelId) || null;
state.fullscreen = state.fullscreen ? true : null;
state.edit = (state.edit === "true" || state.edit === true) || null;
state.editview = state.editview || null;
return state;
};
DashboardViewState.prototype.serializeToUrl = function() {
var urlState = _.clone(this.state);
urlState.fullscreen = this.state.fullscreen ? true : null,
urlState.fullscreen = this.state.fullscreen ? true : null;
urlState.edit = this.state.edit ? true : null;
return urlState;
};
@ -117,7 +118,7 @@ function (angular, _, $) {
self.$scope.dashboard.emit_refresh();
}
else {
self.fullscreenPanel.$emit('render');
self.fullscreenPanel.$broadcast('render');
}
delete self.fullscreenPanel;
});
@ -138,7 +139,7 @@ function (angular, _, $) {
panelScope.fullscreen = true;
$timeout(function() {
panelScope.$emit('render');
panelScope.$broadcast('render');
});
};

View File

@ -13,7 +13,7 @@ function (angular, _, config, kbn, moment) {
module.factory('ElasticDatasource', function($q, $http, templateSrv) {
function ElasticDatasource(datasource) {
this.type = 'elastic';
this.type = 'elasticsearch';
this.basicAuth = datasource.basicAuth;
this.url = datasource.url;
this.name = datasource.name;

View File

@ -0,0 +1,78 @@
define([
'angular',
'lodash',
'kbn',
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('grafana.services');
module.factory('GrafanaDatasource', function($q, backendSrv) {
function GrafanaDatasource() {
this.type = 'grafana';
this.grafanaDB = true;
this.name = "grafana";
this.supportMetrics = true;
this.editorSrc = 'app/features/grafanaDatasource/partials/query.editor.html';
}
GrafanaDatasource.prototype.getDashboard = function(slug, isTemp) {
var url = '/dashboards/' + slug;
if (isTemp) {
url = '/temp/' + slug;
}
return backendSrv.get('/api/dashboards/db/' + slug);
};
GrafanaDatasource.prototype.query = function(options) {
// get from & to in seconds
var from = kbn.parseDate(options.range.from).getTime();
var to = kbn.parseDate(options.range.to).getTime();
return backendSrv.get('/api/metrics/test', { from: from, to: to, maxDataPoints: options.maxDataPoints });
};
GrafanaDatasource.prototype.starDashboard = function(dashId) {
return backendSrv.post('/api/user/stars/dashboard/' + dashId);
};
GrafanaDatasource.prototype.unstarDashboard = function(dashId) {
return backendSrv.delete('/api/user/stars/dashboard/' + dashId);
};
GrafanaDatasource.prototype.saveDashboard = function(dashboard) {
// remove id if title has changed
if (dashboard.title !== dashboard.originalTitle) {
dashboard.id = null;
}
return backendSrv.post('/api/dashboards/db/', { dashboard: dashboard })
.then(function(data) {
return { title: dashboard.title, url: '/dashboard/db/' + data.slug };
}, function(err) {
err.isHandled = true;
err.data = err.data || {};
throw err.data.message || "Unknown error";
});
};
GrafanaDatasource.prototype.deleteDashboard = function(id) {
return backendSrv.delete('/api/dashboards/db/' + id);
};
GrafanaDatasource.prototype.searchDashboards = function(query) {
return backendSrv.get('/api/search/', {q: query})
.then(function(data) {
return data;
});
};
return GrafanaDatasource;
});
});

View File

@ -0,0 +1,17 @@
<div class="fluid-row" style="margin-top: 20px">
<div class="span2"></div>
<div class="grafana-info-box span8">
<h5>Test graph</h5>
<p>
This is just a test data source that generates random walk series. If this is your only data source
open the left side menu and navigate to the data sources admin screen and add your data sources. You can change
data source using the button to the left of the <strong>Add query</strong> button.
</p>
</div>
<div class="span2"></div>
<div class="clearfix"></div>
</div>

View File

@ -15,7 +15,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
module.factory('InfluxDatasource', function($q, $http, templateSrv) {
function InfluxDatasource(datasource) {
this.type = 'influxDB';
this.type = 'influxdb';
this.urls = datasource.urls;
this.username = datasource.username;
this.password = datasource.password;

View File

@ -0,0 +1,6 @@
define([
'./panelMenu',
'./panelDirective',
'./panelSrv',
'./soloPanelCtrl',
], function () {});

View File

@ -0,0 +1,40 @@
define([
'angular',
'jquery',
'config',
],
function (angular, $, config) {
'use strict';
angular
.module('grafana.directives')
.directive('panelLoader', function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem, attr) {
var getter = $parse(attr.type), panelType = getter(scope);
var panelPath = config.panels[panelType].path;
scope.require([panelPath + "/module"], function () {
var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
elem.append(panelEl);
$compile(panelEl)(scope);
});
}
};
}).directive('grafanaPanel', function() {
return {
restrict: 'E',
templateUrl: 'app/features/panel/partials/panel.html',
transclude: true,
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
scope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
panelContainer.css({ minHeight: scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
});
}
};
});
});

View File

@ -1,8 +1,9 @@
define([
'angular',
'lodash',
'config',
],
function (angular, _) {
function (angular, _, config) {
'use strict';
var module = angular.module('grafana.services');
@ -24,7 +25,7 @@ function (angular, _) {
$scope.sharePanel = function() {
$scope.appEvent('show-modal', {
src: './app/partials/share-panel.html',
src: './app/features/dashboard/partials/shareModal.html',
scope: $scope.$new()
});
};
@ -77,6 +78,10 @@ function (angular, _) {
$scope.editorHelpIndex = index;
};
$scope.isNewPanel = function() {
return $scope.panel.title === config.new_panel_title;
};
$scope.toggleFullscreen = function(edit) {
$scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
};

View File

@ -0,0 +1,42 @@
<div class="panel-container">
<div class="panel-header">
<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="panelMeta.error">
<span data-placement="top" bs-tooltip="panelMeta.error">
<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>
</span>
</span>
<span class="panel-loading" ng-show="panelMeta.loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
<div class="panel-title-container drag-handle" panel-menu></div>
</div>
<div class="panel-content">
<ng-transclude></ng-transclude>
</div>
</div>
<div class="panel-full-edit" ng-if="editMode">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i ng-class="panelMeta.editIcon"></i>
{{panelMeta.panelName}}
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="gf-box-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="container-fluid main">
<div class="row-fluid">
<div class="span12">
<div class="panel nospace" ng-if="panel" style="width: 100%">
<panel-loader type="panel.type" ng-cloak></panel-loader>
</div>
</div>
</div>

View File

@ -0,0 +1,70 @@
define([
'angular',
'jquery',
],
function (angular, $) {
"use strict";
var module = angular.module('grafana.routes');
module.controller('SoloPanelCtrl', function($scope, $rootScope, datasourceSrv, $routeParams, dashboardSrv, timeSrv, $location) {
var panelId;
$scope.init = function() {
var db = datasourceSrv.getGrafanaDB();
var params = $location.search();
panelId = parseInt(params.panelId);
db.getDashboard($routeParams.id, false)
.then(function(dashboard) {
$scope.initPanelScope(dashboard);
}).then(null, function(error) {
$scope.appEvent('alert-error', ['Load panel error', error]);
});
};
$scope.initPanelScope = function(dashboard) {
$scope.dashboard = dashboardSrv.create(dashboard.model);
$scope.grafana.style = $scope.dashboard.style;
$scope.row = {
height: $(window).height() + 'px',
};
$scope.test = "Hej";
$scope.$index = 0;
$scope.panel = $scope.getPanelById(panelId);
if (!$scope.panel) {
$scope.appEvent('alert-error', ['Panel not found', '']);
return;
}
$scope.panel.span = 12;
$scope.dashboardViewState = {
registerPanel: function() {
}
};
timeSrv.init($scope.dashboard);
};
$scope.getPanelById = function(id) {
var rows = $scope.dashboard.rows;
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
for (var j = 0; j < row.panels.length; j++) {
var panel = row.panels[j];
if (panel.id === id) {
return panel;
}
}
}
return null;
};
if (!$scope.skipAutoInit) {
$scope.init();
}
});
});

View File

@ -0,0 +1,127 @@
<topnav toggle="toggleSideMenu()"
title="Details"
icon="fa fa-user"
section="Profile"
show-menu-btn="!grafana.sidemenu">
</topnav>
<div class="editor-row">
<div class="section">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-user"></i>
Personal information
</div>
</div>
<div class="gf-box-body">
<div class="row">
<form name="userForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Name</strong>
</li>
<li>
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Email</strong>
</li>
<li>
<input type="text" required ng-model="user.email" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Username</strong>
</li>
<li>
<input type="text" required ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
</form>
</div>
</div>
</div>
</div>
<div class="section">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-shield"></i>
Your accounts
</div>
</div>
<div class="gf-box-body">
<table class="grafana-options-table">
<tr ng-repeat="ac in accounts">
<td>Name: {{ac.name}}</td>
<td>Role: {{ac.role}}</td>
<td ng-show="ac.isUsing">
<span class="label label-info">
active now
</span>
</td>
<td ng-show="!ac.isUsing">
<a ng-click="setUsingAccount(ac)" class="btn btn-success btn-mini">
Select
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="section">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-plus-square"></i>
Add account
</div>
</div>
<div class="gf-box-body">
<form name="form">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
<strong>Account name</strong>
</li>
<li>
<input type="text" ng-model="newAccount.name" required class="input-xlarge tight-form-input" placeholder="account name">
</li>
<li>
<button class="btn btn-success tight-form-btn" ng-click="createAccount()">Create</button>
</li>
</ul>
<div class="clearfix"></div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,46 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('ProfileCtrl', function($scope, $http, backendSrv) {
$scope.newAccount = {name: ''};
$scope.init = function() {
$scope.getUser();
$scope.getUserAccounts();
};
$scope.getUser = function() {
backendSrv.get('/api/user').then(function(user) {
$scope.user = user;
});
};
$scope.getUserAccounts = function() {
backendSrv.get('/api/user/accounts').then(function(accounts) {
$scope.accounts = accounts;
});
};
$scope.setUsingAccount = function(account) {
backendSrv.post('/api/user/using/' + account.accountId).then($scope.getUserAccounts);
};
$scope.update = function() {
if (!$scope.userForm.$valid) { return; }
backendSrv.put('/api/user/', $scope.user);
};
$scope.createAccount = function() {
backendSrv.post('/api/account/', $scope.newAccount).then($scope.getUserAccounts);
};
$scope.init();
});
});

View File

@ -0,0 +1,41 @@
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px">
<strong>Mode</strong>
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.mode" ng-options="f for f in modes" ng-change="get_data()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="section" style="margin-bottom: 20px" ng-if="panel.mode === 'search'">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px">
<strong>Search options</strong>
</li>
<li class="tight-form-item">
Query
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="title query"
ng-model="panel.query" ng-change="get_data()" ng-model-onblur>
</li>
<li class="tight-form-item">
Tag
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="full tag name"
ng-model="panel.tag" ng-change="get_data()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
<grafana-panel>
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in dashList">
<a class="dashlist-link" href="{{dash.url}}">
<span class="dashlist-title">
{{dash.title}}
</span>
<span class="dashlist-star">
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': !dash.isStarred}"></i>
</span>
</a>
</div>
</grafana-panel>

View File

@ -0,0 +1,69 @@
define([
'angular',
'app',
'lodash',
'config',
'components/panelmeta',
],
function (angular, app, _, config, PanelMeta) {
'use strict';
var module = angular.module('grafana.panels.dashlist', []);
app.useModule(module);
module.directive('grafanaPanelDashlist', function() {
return {
controller: 'DashListPanelCtrl',
templateUrl: 'app/panels/dashlist/module.html',
};
});
module.controller('DashListPanelCtrl', function($scope, panelSrv, backendSrv) {
$scope.panelMeta = new PanelMeta({
panelName: 'Dash list',
editIcon: "fa fa-star",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Options', 'app/panels/dashlist/editor.html');
var defaults = {
mode: 'starred',
query: '',
tag: '',
};
$scope.modes = ['starred', 'search'];
_.defaults($scope.panel, defaults);
$scope.dashList = [];
$scope.init = function() {
panelSrv.init($scope);
if ($scope.isNewPanel()) {
$scope.panel.title = "Starred Dashboards";
}
$scope.$on('refresh', $scope.get_data);
};
$scope.get_data = function() {
var params = {};
if ($scope.panel.mode === 'starred') {
params.starred = 1;
} else {
params.q = "tags:" + $scope.panel.tag + " AND title:" + $scope.panel.query;
}
backendSrv.get('/api/search', params).then(function(result) {
$scope.dashList = result.dashboards;
$scope.panelMeta.loading = false;
});
};
$scope.init();
});
});

View File

@ -1,4 +1,4 @@
<div ng-controller='GraphCtrl'>
<grafana-panel>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
@ -24,23 +24,6 @@
<div class="clearfix"></div>
<div style="margin-top: 30px" ng-if="editMode">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-bar-chart"></i>
Graph
</div>
</grafana-panel>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
</div>

View File

@ -16,17 +16,25 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
var module = angular.module('grafana.panels.graph');
module.directive('grafanaPanelGraph', function() {
return {
controller: 'GraphCtrl',
templateUrl: 'app/panels/graph/module.html',
};
});
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
$scope.panelMeta = new PanelMeta({
description: 'Graph panel',
panelName: 'Graph',
editIcon: "fa fa-bar-chart",
fullscreen: true,
metricsEditor: true
});
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
$scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/dashboard/partials/panelTime.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');

View File

@ -1,26 +1,4 @@
<div ng-controller='SingleStatCtrl'>
<grafana-panel>
<div class="singlestat-panel" singlestat-panel></div>
<div class="clearfix"></div>
<div style="margin-top: 30px" ng-if="editMode">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-dashboard"></i>
Singlestat
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
</div>
</grafana-panel>

View File

@ -13,10 +13,18 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) {
var module = angular.module('grafana.panels.singlestat');
app.useModule(module);
module.directive('grafanaPanelSinglestat', function() {
return {
controller: 'SingleStatCtrl',
templateUrl: 'app/panels/singlestat/module.html',
};
});
module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) {
$scope.panelMeta = new PanelMeta({
description: 'Singlestat panel',
panelName: 'Singlestat',
editIcon: "fa fa-dashboard",
fullscreen: true,
metricsEditor: true
});
@ -192,7 +200,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) {
data.colorMap = $scope.panel.colors;
$scope.data = data;
$scope.$emit('render');
$scope.$broadcast('render');
};
$scope.getFormatedValue = function(mainValue) {

View File

@ -1,4 +1,3 @@
<div ng-controller='text'>
<p ng-bind-html="content" ng-show="content">
</p>
</div>
<grafana-panel>
<p ng-bind-html="content" ng-show="content"></p>
</grafana-panel>

View File

@ -8,15 +8,24 @@ define([
function (angular, app, _, require, PanelMeta) {
'use strict';
var converter;
var module = angular.module('grafana.panels.text', []);
app.useModule(module);
var converter;
module.directive('grafanaPanelText', function() {
return {
controller: 'TextPanelCtrl',
templateUrl: 'app/panels/text/module.html',
};
});
module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
module.controller('TextPanelCtrl', function($scope, templateSrv, $sce, panelSrv) {
$scope.panelMeta = new PanelMeta({
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
panelName: 'Text',
editIcon: "fa fa-text-width",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html');

View File

@ -1,11 +1,14 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-clock-o"></i>
Custom time range
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<style>
.timepicker-to-column {
margin-top: 10px;
@ -73,12 +76,9 @@
</form>
<div class="clearfix"></div>
</div>
</div>
<div class="dashboard-editor-footer">
<form name="input" style="margin-bottom:0">
<span class="" ng-hide="input.$valid">Invalid date or range</span>
<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
<button ng-click="dismiss();" class="btn btn-success pull-right">Cancel</button>
</form>
</div>

View File

@ -1,5 +1,6 @@
<div class="editor-row">
<div class="section">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 148px">
@ -34,6 +35,7 @@
</ul>
<div class="clearfix"></div>
</div>
</div>
<p>
<br>

View File

@ -21,6 +21,7 @@
<li class="dropdown">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
<i class="fa fa-clock-o"></i>
<span ng-bind="time.rangeString"></span>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
<i class="fa fa-caret-down"></i>

View File

@ -1,12 +1,12 @@
<div class="modal-body">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="modal-body gf-box gf-box-no-margin">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-check"></i>
{{title}}
</div>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body" style="min-height: 0px;">
<p class="row-fluid text-center large">
{{text}}
<br>

View File

@ -1,11 +0,0 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>{{share.title}} <small>shareable link</small></h3>
</div>
<div class="modal-body">
<label>Share this dashboard with this URL</label>
<input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>
</div>

View File

@ -35,8 +35,8 @@
<li class="dropdown-submenu">
<a href="javascript:void(0);">Add Panel</a>
<ul class="dropdown-menu">
<li bindonce ng-repeat="name in panelNames">
<a ng-click="add_panel_default(name)" bo-text="name"></a>
<li bindonce ng-repeat="(key, value) in panels">
<a ng-click="add_panel_default(key)" bo-text="value.name"></a>
</li>
</ul>
</li>
@ -79,12 +79,11 @@
</div>
<!-- Panels, draggable needs to be disabled in fullscreen because Firefox bug -->
<div ng-repeat="(name, panel) in row.panels"
class="panel"
<div ng-repeat="(name, panel) in row.panels" class="panel"
ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)"
drag-handle-class="drag-handle" panel-width ng-model="panel">
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
drag-handle-class="drag-handle" panel-width>
<panel-loader type="panel.type" class="panel-margin"></panel-loader>
</div>
<div panel-drop-zone class="panel panel-drop-zone"

View File

@ -1,12 +1,53 @@
<div class="navbar navbar-static-top">
<div class="navbar navbar-static-top" ng-controller='DashboardNavCtrl' ng-init="init()">
<div class="navbar-inner">
<div class="container-fluid">
<span class="brand">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<span class="page-title">{{dashboard.title}}</span>
</span>
<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
<div class="top-nav">
<a class="pointer top-nav-menu-btn" ng-if="!grafana.sidemenu" ng-click="toggleSideMenu()">
<img class="logo-icon" src="img/fav32.png"></img>
<i class="fa fa-bars"></i>
</a>
<div class="top-nav-dashboards-btn">
<a class="pointer" ng-click="openSearch()">
<i class="fa fa-th-large"></i>
<i class="fa fa-caret-down"></i>
</a>
</div>
<span class="fa fa-angle-right top-nav-breadcrumb-icon">
</span>
<a ng-click="asd()" class="top-nav-title pointer">
{{dashboard.title}}
</a>
</div>
<ul class="nav pull-left" ng-if="!dashboardMeta.isHome">
<li>
<a class="pointer" ng-click="starDashboard()">
<i class="fa" ng-class="{'fa-star-o': !dashboardMeta.isStarred, 'fa-star': dashboardMeta.isStarred,}" style="color: orange;"></i>
</a>
</li>
<li>
<a class="pointer" ng-click="shareDashboard()"><i class="fa fa-share-square-o"></i></a>
</li>
<li>
<a ng-click="saveDashboard()"><i class="fa fa-save"></i></a>
</li>
<li class="dropdown">
<a class="pointer" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
<ul class="dropdown-menu">
<li><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
<li><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
<li><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
<li><a class="pointer" ng-click="exportDashboard();">Export</a></li>
<li><a class="pointer" ng-click="editJson();">View JSON</a></li>
</ul>
</li>
</ul>
<ul class="nav pull-right">
<li ng-show="dashboardViewState.fullscreen">
<a ng-click="exitFullscreen()">
Back to dashboard
@ -18,55 +59,6 @@
</grafana-simple-panel>
</li>
<li class="dropdown grafana-menu-save">
<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
<i class='fa fa-save'></i>
</a>
<ul class="save-dashboard-dropdown dropdown-menu" ng-if="saveDropdownOpened">
<li>
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
<input class='input-medium' ng-model="dashboard.title" type="text" />
<button class="btn" ng-click="saveDashboard()"><i class="fa fa-save"></i></button>
</form>
</li>
<li>
<a class="link" ng-click="set_default()">Save as Home</a>
</li>
<li>
<a class="link" ng-click="purge_default()">Reset Home</a>
</li>
<li ng-show="!isFavorite">
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
</li>
<li ng-show="isFavorite">
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
</li>
<li>
<a class="link" ng-click="editJson()">Dashboard JSON</a>
</li>
<li>
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
</li>
<li ng-show="db.saveTemp">
<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
Share temp copy
</a>
</li>
</ul>
</li>
<li class="dropdown grafana-menu-load">
<a ng-click="openSearch()" bs-tooltip="'Search'" data-placement="bottom">
<i class='fa fa-folder-open'></i>
</a>
</li>
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='fa fa-home'></i></a></li>
<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" dash-editor-link="app/partials/dasheditor.html"><i class='fa fa-cog pointer'></i></a></li>
<li class="grafana-menu-stop-playlist hide">
<a class='small' ng-click='stopPlaylist(2)'>
Stop playlist

View File

@ -1,7 +1,7 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-cogs"></i>
Dashboard settings
Settings
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
@ -11,9 +11,13 @@
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div ng-if="editor.index == 0">
<div class="editor-row">
@ -93,14 +97,12 @@
<div class="clearfix"></div>
</div>
<div class="dashboard-editor-footer">
<div class="gf-box-footer">
<div class="grafana-version-info" ng-show="editor.index === 0">
<span class="editor-option small">
Grafana version: {{grafanaVersion}} &nbsp;&nbsp;
Grafana version: {{grafana.version}} &nbsp;&nbsp;
</span>
<span grafana-version-check>
</span>
</div>
<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss();reset_panel();dashboard.emit_refresh()">Close</button>
</div>

View File

@ -1,19 +1,22 @@
<div ng-controller="JsonEditorCtrl">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-edit"></i>
JSON
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body" style="height: 500px">
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 90%;"></textarea>
</div>
<div class="gf-box-body" style="height: 500px">
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 100%;"></textarea>
<br>
<br>
<div class="dashboard-editor-footer">
<button type="button" class="btn btn-success pull-left" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="row-fluid" style="margin-top: 100px;">
<div class="span2"></div>
<div class="grafana-info-box span8 text-center">
<h3>Page not found (404)</h3>
</div>
<div class="span2"></div>
</div>

View File

@ -1,12 +1,16 @@
<div class="modal-body">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="modal-body gf-box-no-margin">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-keyboard-o"></i>
Keyboard shutcuts
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<table class="shortcut-table">
<tr>
<th></th>
@ -45,6 +49,3 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
</div>

View File

@ -1,6 +1,6 @@
<div class="modal-body" ng-controller="InspectCtrl" ng-init="init()">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="modal-body gf-box gf-box-no-margin" ng-controller="InspectCtrl" ng-init="init()">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-eye"></i>
Inspector
</div>
@ -12,7 +12,7 @@
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div ng-if="editor.index == 0">
<h5>Request details</h5>
@ -72,9 +72,5 @@
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
</div>

View File

@ -1,36 +0,0 @@
<div ng-controller="MetricKeysCtrl" ng-init="init()">
<h5>Load metrics keys into elastic search</h5>
<p>
Work in progress...
</p>
<!-- <div class="row-fluid">
<div class="span12">
<label class="small">Load metrics recursive starting from this metric path</label>
<input type="text" class="input-xlarge" ng-model="metricPath"> </input>
</div>
</div>
<div class="row-fluid" style="margin-top: 15px;">
<div class="span12">
<button class="btn btn-success" ng-click="createIndex()">Clear/Create index</button>
<button class="btn btn-success" ng-click="loadMetricsFromPath()">Load from metric path</button>
<button class="btn btn-danger" ng-click="loadAll()">Load all</button>
<tip>Load all will fetch all metrics in one call, can be intensive for graphite and for the browser if you have a lot of metrics</tip>
</div>
</div>
<div class="row-fluid" style="margin-top: 15px;">
<div class="span12" ng-show="infoText" style="padding-top: 10px;">
{{infoText}}
</div>
<div class="span12 alert alert-error" ng-show="errorText">
{{errorText}}
</div>
</div>
<div class="row-fluid" ng-show="metricCounter">
<div class="span12" style="padding-top: 10px;">
Metrics indexed: {{metricCounter}}
</div>
</div> -->
</div>

103
src/app/partials/login.html Normal file
View File

@ -0,0 +1,103 @@
<div class="container">
<div class="login-box">
<div class="login-box-logo">
<img src="img/logo_transparent_200x75.png">
</div>
<div class="login-inner-box">
<div class="login-tab-header">
<button class="btn-login-tab" ng-click="loginMode = true;" ng-class="{active: loginMode}">
Log in
</button>
<button class="btn-login-tab" ng-click="loginMode = false;" ng-class="{active: !loginMode}" ng-show="!disableUserSignUp">
Sign up
</button>
</div>
<form name="loginForm" class="login-form">
<div class="tight-form" ng-if="loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>User</strong>
</li>
<li>
<input type="text" name="username" class="tight-form-input last" required ng-model='formModel.user' placeholder="email or username" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Password</strong>
</li>
<li>
<input type="password" name="password" class="tight-form-input last" required ng-model="formModel.password" id="inputPassword" style="width: 246px" placeholder="password">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="!loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Email</strong>
</li>
<li>
<input type="email" class="tight-form-input last" required ng-model='formModel.email' placeholder="email" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="!loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Password</strong>
</li>
<li>
<input type="password" class="tight-form-input last" watch-change="passwordChanged(inputValue)" ng-minlength="4" required ng-model='formModel.password' placeholder="password" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">
<em>{{strengthText}}</em>
</div>
<div class="login-submit-button-row">
<button type="submit" class="btn" ng-click="submit();"
ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
{{submitBtnText}}
</button>
</div>
</form>
<div class="clearfix"></div>
<div class="login-oauth text-center">
<a class="btn btn-google" href="login/google" target="_self" ng-if="googleAuthEnabled">
<i class="fa fa-google"></i>
with Google
</a>
<a class="btn btn-github" href="login/github" target="_self" ng-if="githubAuthEnabled">
<i class="fa fa-github"></i>
with Github
</a>
</div>
</div>
<div class="row" style="margin-top: 100px">
<div class="version-footer text-center small">
Grafana version: {{buildInfo.version}}, commit: {{buildInfo.commit}},
build date: {{buildInfo.buildstamp | date: 'yyyy-MM-dd HH:mm:ss' }}
</div>
</div>
</div>

View File

@ -0,0 +1,14 @@
<div class="navbar navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<topnav toggle="toggleSideMenu()"
title="{{pageTitle}}"
icon="{{pageIcon}}"
section="{{pageSection}}"
show-menu-btn="!grafana.sidemenu">
</topnav>
</div>
</div>
</div>

View File

@ -1,22 +0,0 @@
<div bindonce class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-text-width"></i>
<span bo-text="panel.type+' settings'"></span>
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editor.index == $index">
<div ng-include src="tab.src"></div>
</div>
</div>
<div class="dashboard-editor-footer">
<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss()">Close</button>
</div>

View File

@ -1,12 +1,17 @@
<div ng-controller="PlaylistCtrl" ng-init="init()">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-play"></i>
Start dashboard playlist
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div class="editor-row">
<div class="section">
@ -52,11 +57,11 @@
<input type="text" class="input-small" ng-model="timespan" />
</div>
</div>
</div>
</div>
<div class="dashboard-editor-footer">
<button class="btn btn-success" ng-click="start();dismiss();"><i class="fa fa-play"></i> Start</button>
<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
</div>
<br>
<button class="btn btn-success pull-right" ng-click="start();dismiss();"><i class="fa fa-play"></i> Start</button>
<br>
</div>
</div>

View File

@ -1,56 +1,54 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-th-list"></i>
Row settings
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['General','Panels']" data-title="{{tab}}">
<div ng-repeat="tab in ['General','Panels']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div class="editor-row" ng-if="editor.index == 0">
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
</div>
<div class="editor-option">
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
</div>
<div class="editor-row" ng-if="editor.index == 0">
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
</div>
<div class="editor-option">
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
</div>
<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
</div>
<div class="row-fluid" ng-if="editor.index == 1">
<div class="span12">
<div class="row-fluid" ng-if="editor.index == 1">
<div class="span12">
<table class="grafana-options-table" style="max-width: 400px; width: auto">
<thead>
<th>Title</th>
<th>Type</th>
<th>Span</span></th>
<th></th>
<th></th>
<th></th>
</thead>
<tr ng-repeat="panel in row.panels">
<td style="width: 95%">{{panel.title}}</td>
<td>{{panel.type}}</td>
<td><select ng-hide="panel.sizeable == false" class="input-mini" style="margin-bottom: 0;" ng-model="panel.span" ng-options="size for size in [1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td>
<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
<thead>
<th>Title</th>
<th>Type</th>
<th>Span</span></th>
<th></th>
<th></th>
<th></th>
</thead>
<tr ng-repeat="panel in row.panels">
<td style="width: 95%">{{panel.title}}</td>
<td>{{panel.type}}</td>
<td><select ng-hide="panel.sizeable == false" class="input-mini" style="margin-bottom: 0;" ng-model="panel.span" ng-options="size for size in [1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td>
<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
<div class="dashboard-editor-footer">
<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss();reset_panel();close_edit()">Close</button>
</div>

View File

@ -1,7 +1,7 @@
<div ng-controller="SearchCtrl" ng-init="init()">
<div ng-controller="SearchCtrl" ng-init="init()" class="search-box">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title" style="border: 0; line-height: 41px;">
<div class="gf-box-header">
<div class="gf-box-title" style="border: 0; line-height: 41px;">
<i class="fa fa-search"></i>
Search
</div>
@ -30,7 +30,6 @@
</div>
<div ng-if="!showImport">
<h6 ng-hide="results.dashboards.length">No dashboards matching your query were found.</h6>
<div class="search-results-container" ng-if="tagsOnly">
<div class="row">
<div class="span6 offset1">
@ -47,13 +46,12 @@
</div>
<div class="search-results-container" ng-if="!tagsOnly">
<h6 ng-hide="results.dashboards.length">No dashboards matching your query were found.</h6>
<div class="search-result-item pointer" bindonce ng-repeat="row in results.dashboards"
ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.id)">
ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.slug)">
<div class="search-result-actions small">
<a ng-click="shareDashboard(row.id, row.id, $event)" config-modal="app/partials/dashLoaderShare.html">
<i class="fa fa-share"></i> share &nbsp;&nbsp;&nbsp;
</a>
<a ng-click="deleteDashboard(row, $event)">
<i class="fa fa-remove"></i> delete
</a>
@ -63,6 +61,7 @@
<a ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name class="label label-tag">
{{tag}}
</a>
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
</div>
<a class="search-result-link">

View File

@ -1,33 +0,0 @@
<div ng-controller="SharePanelCtrl">
<div class="modal-header">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-share"></i>
Share
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
</div>
</div>
</div>
</div>
<div class="modal-body">
<div class="editor-row">
<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
</div>
<div class="editor-row" style="margin-top: 20px;">
<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success pull-right" ng-click="dismiss();">close</button>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="modal-body gf-box gf-box-no-margin">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-share-alt"></i>
Share dashboard
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="gf-box-body">
<label>Share this dashboard with this URL</label>
<input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
</div>
</div>

View File

@ -0,0 +1,39 @@
<div ng-controller="SideMenuCtrl" ng-init="init()">
<ul class="sidemenu">
<li style="margin-bottom: 2px;">
<a class="pointer sidemenu-top-btn" ng-click="toggleSideMenu()">
<img class="logo-icon" src="img/fav32.png"></img>
<i class="pull-right fa fa-angle-left"></i>
</a>
</li>
<li ng-repeat="item in menu">
<a href="{{item.href}}" class="sidemenu-item" target="{{item.target}}">
<span class="sidemenu-icon"><i class="{{item.icon}}"></i></span>
<span class="sidemenu-item-text">{{item.text}}</span>
</a>
</li>
<li ng-if="grafana.user.isSignedIn" style="margin-top:50px">
<a href="profile" class="sidemenu-item sidemenu-user">
<img ng-src="{{grafana.user.gravatarUrl}}">
<span class="sidemenu-item-text">{{grafana.user.name}}</a>
</a>
</li>
<li ng-if="grafana.user.isSignedIn">
<a href="logout" class="sidemenu-item" target="_self">
<span class="sidemenu-item-text no-icon">Sign out</span>
</a>
</li>
<li ng-if="!grafana.user.isSignedIn" style="margin-top:50px">
<a href="/login" class="sidemenu-item" target="_self">
<span class="sidemenu-icon"><i class="fa fa-sign-in"></i></span>
<span class="sidemenu-item-text">Sign in</span>
</a>
</li>
</ul>
</div>

View File

@ -1,20 +1,6 @@
<div class="submenu-controls" ng-controller="SubmenuCtrl">
<div class="tight-form" style="border-top: none">
<ul class="tight-form-list">
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer" data-toggle="dropdown">
<i class="fa fa-cog"></i>
</a>
<ul class="dropdown-menu">
<li><a class="pointer" dash-editor-link="app/partials/templating_editor.html">Templating</a></li>
<li><a class="pointer" dash-editor-link="app/features/annotations/partials/editor.html">Annotations</a></li>
</ul>
</div>
</li>
</ul>
<ul class="tight-form-list" ng-if="dashboard.templating.enable">
<li ng-repeat-start="variable in variables" class="tight-form-item template-param-name">
<span class="template-variable ">

View File

@ -1,17 +1,23 @@
<div ng-controller="TemplateEditorCtrl" ng-init="init()"> <div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<div ng-controller="TemplateEditorCtrl" ng-init="init()">
<div class="gf-box-header">
<div class="gf-box-title">
<i class="fa fa-code"></i>
Templating
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Variables', 'Add', 'Edit']" data-title="{{tab}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="dashboard-editor-body">
<div class="gf-box-body">
<div ng-if="editor.index == 0">
@ -50,104 +56,97 @@
</div>
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
<div class="row">
<div class="editor-option">
<div class="editor-option">
<div class="editor-row">
<div class="editor-option">
<label class="small">Variable name</label>
<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
</div>
<div class="editor-option" ng-show="current.type === 'query'">
<label class="small">Datasource</label>
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
</div>
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
model="current.refresh"></editor-opt-bool>
</div>
<div ng-show="current.type === 'interval'">
<div class="editor-row">
<div class="editor-option">
<label class="small">Variable name</label>
<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
</div>
<div class="editor-option" ng-show="current.type === 'query'">
<label class="small">Datasource</label>
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
</div>
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
model="current.refresh"></editor-opt-bool>
</div>
<div ng-show="current.type === 'interval'">
<div class="editor-row">
<div class="editor-option">
<label class="small">Values</label>
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
</div>
</div>
<div class="editor-row">
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.auto">
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
</div>
<label class="small">Values</label>
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
</div>
</div>
<div ng-show="current.type === 'custom'">
<div class="editor-row">
<div class="editor-option">
<label class="small">Values seperated by comma</label>
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
</div>
</div>
</div>
<div ng-show="current.type === 'query'">
<div class="editor-row">
<div class="editor-option form-inline">
<label class="small">Variable values query</label>
<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
</div>
</div>
<div class="editor-row" style="margin: 15px 0">
<div class="editor-option form-inline">
<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
</div>
</div>
<div class="editor-row" style="margin: 15px 0">
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.includeAll">
<label class="small">All format</label>
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
</div>
<div class="editor-option" ng-show="current.includeAll">
<label class="small">All value</label>
<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
</div>
<div class="editor-row">
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.auto">
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
</div>
</div>
</div>
<div class="editor-option">
<div ng-show="current.type === 'custom'">
<div class="editor-row">
<div class="editor-option" >
<label class="small">Variable values (showing 20/{{current.options.length}})</label>
<ul class="grafana-options-list">
<li ng-repeat="option in current.options | limitTo: 20">
{{option.text}}
</li>
</ul>
<div class="editor-option">
<label class="small">Values seperated by comma</label>
<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
</div>
</div>
</div>
<div ng-show="current.type === 'query'">
<div class="editor-row">
<div class="editor-option form-inline">
<label class="small">Variable values query</label>
<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
</div>
</div>
<div class="editor-row" style="margin: 15px 0">
<div class="editor-option form-inline">
<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
</div>
</div>
<div class="editor-row" style="margin: 15px 0">
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.includeAll">
<label class="small">All format</label>
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
</div>
<div class="editor-option" ng-show="current.includeAll">
<label class="small">All value</label>
<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
</div>
</div>
</div>
</div>
<div class="editor-option">
<div class="editor-row">
<div class="editor-option" >
<label class="small">Variable values (showing 20/{{current.options.length}})</label>
<ul class="grafana-options-list">
<li ng-repeat="option in current.options | limitTo: 20">
{{option.text}}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="dashboard-editor-footer">
<button type="button" class="btn btn-success pull-left" ng-show="editor.index === 2" ng-click="update();">Update</button>
<button type="button" class="btn btn-success pull-left" ng-show="editor.index === 1" ng-click="add();">Add</button>
<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
<button type="button" class="btn btn-success" ng-show="editor.index === 2" ng-click="update();">Update</button>
<button type="button" class="btn btn-success" ng-show="editor.index === 1" ng-click="add();">Add</button>
</div>
</div>

View File

@ -1,18 +1,20 @@
<div class="modal-header">
</div>
<div class="modal-body">
<h4 class="text-center"><i class="fa fa-warning"></i> Unsaved changes</h4>
<div class="row-fluid">
<span class="span3">
{{changes}}
<div class="modal-body gf-box gf-box-no-margin">
<div class="gf-box-header text-center">
<span class="gf-box-title">
<i class="fa fa-exclamation"></i>
Unsaved changes
</span>
<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
<span class="span3"></span>
</div>
<button class="gf-box-header-close-btn" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="gf-box-body" style="min-height: 0;">
<div class="row-fluid text-center">
<button type="button" class="btn btn-success span4" ng-click="save();dismiss();">Save</button>
<button type="button" class="btn btn-danger span4" ng-click="ignore();dismiss();">Ignore</button>
<button type="button" class="btn btn-inverse span4" ng-click="dismiss()">Cancel</button>
</div>
</div>
</div>
<div class="modal-footer">
</div>

View File

@ -0,0 +1,79 @@
define([
'angular',
'./dashboard',
], function(angular) {
"use strict";
var module = angular.module('grafana.routes');
module.config(function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromDBProvider',
reloadOnSearch: false,
})
.when('/dashboard/db/:id', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromDBProvider',
reloadOnSearch: false,
})
.when('/dashboard/import/:id', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromImportCtrl',
reloadOnSearch: false,
})
.when('/dashboard/new', {
templateUrl: 'app/partials/dashboard.html',
controller : 'NewDashboardCtrl',
reloadOnSearch: false,
})
.when('/account', {
templateUrl: 'app/features/account/partials/account.html',
controller : 'AccountCtrl',
})
.when('/account/datasources', {
templateUrl: 'app/features/account/partials/datasources.html',
controller : 'DataSourcesCtrl',
})
.when('/account/users', {
templateUrl: 'app/features/account/partials/users.html',
controller : 'AccountUsersCtrl',
})
.when('/account/apikeys', {
templateUrl: 'app/features/account/partials/apikeys.html',
controller : 'ApiKeysCtrl',
})
.when('/account/import', {
templateUrl: 'app/features/account/partials/import.html',
controller : 'ImportCtrl',
})
.when('/profile', {
templateUrl: 'app/features/profile/partials/profile.html',
controller : 'ProfileCtrl',
})
.when('/admin/settings', {
templateUrl: 'app/features/admin/partials/settings.html',
controller : 'AdminSettingsCtrl',
})
.when('/admin/users', {
templateUrl: 'app/features/admin/partials/users.html',
controller : 'AdminUsersCtrl',
})
.when('/login', {
templateUrl: 'app/partials/login.html',
controller : 'LoginCtrl',
})
.when('/dashboard/solo/:id/', {
templateUrl: 'app/features/panel/partials/soloPanel.html',
controller : 'SoloPanelCtrl',
})
.otherwise({
templateUrl: 'app/partials/error.html',
controller: 'ErrorCtrl'
});
});
});

View File

@ -0,0 +1,54 @@
define([
'angular',
'store',
],
function (angular) {
"use strict";
var module = angular.module('grafana.routes');
module.controller('DashFromDBProvider', function($scope, datasourceSrv, $routeParams, backendSrv) {
var db = datasourceSrv.getGrafanaDB();
if (!$routeParams.id) {
backendSrv.get('/api/dashboards/home').then(function(result) {
$scope.initDashboard(result, $scope);
},function() {
$scope.initDashboard({}, $scope);
$scope.appEvent('alert-error', ['Load dashboard failed', '']);
});
return;
}
db.getDashboard($routeParams.id, false).then(function(result) {
$scope.initDashboard(result, $scope);
}).then(null, function() {
$scope.initDashboard({
meta: {},
model: { title: 'Not found' }
}, $scope);
});
});
module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) {
if (!window.grafanaImportDashboard) {
alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000);
$location.path('');
return;
}
$scope.initDashboard({ meta: {}, model: window.grafanaImportDashboard }, $scope);
});
module.controller('NewDashboardCtrl', function($scope) {
$scope.initDashboard({
meta: {},
model: {
title: "New dashboard",
rows: [{ height: '250px', panels:[] }]
},
}, $scope);
});
});

View File

@ -0,0 +1,24 @@
define([
'angular',
'config',
'store',
'./fromDB',
'./fromFile',
'./fromScript',
],
function (angular, config, store) {
'use strict';
var module = angular.module('grafana.routes.standalone');
module.config(function($routeProvider) {
$routeProvider
.otherwise({ redirectTo: config.default_route })
.when('/', {
redirectTo: function() {
return store.get('grafanaDashboardDefault') || config.default_route;
}
});
});
});

View File

@ -1,23 +0,0 @@
define([
'angular',
'config',
'store',
'./fromDB',
'./fromFile',
'./fromScript',
],
function (angular, config, store) {
'use strict';
var module = angular.module('grafana.routes');
module.config(function($routeProvider) {
$routeProvider
.when('/', {
redirectTo: function() {
return store.get('grafanaDashboardDefault') || config.default_route;
}
});
});
});

View File

@ -4,7 +4,7 @@ define([
function (angular) {
"use strict";
var module = angular.module('grafana.routes');
var module = angular.module('grafana.routes.standalone');
module.config(function($routeProvider) {
$routeProvider

Some files were not shown because too many files have changed in this diff Show More