diff --git a/Berksfile b/Berksfile deleted file mode 100644 index acc59cb36..000000000 --- a/Berksfile +++ /dev/null @@ -1,3 +0,0 @@ -cookbook 'apt' -cookbook 'postgresql' -cookbook 'redash', git: 'git@github.com:EverythingMe/chef-redash.git' diff --git a/README.md b/README.md index 380953b49..1dd045b8c 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,8 @@ It's very likely that in the future we will switch to [D3.js](http://d3js.org/) ## Getting Started -1. Download the [latest release](https://github.com/everythingme/redash/releases). -2. Make sure you have `Python` v2.7, `pip`, PostgreSQL and Redis installed. -3. Install Python requirements: `pip install -r requirements.txt`. -4. Make a copy of the examples settings file: `cp redash/settings_example.py redash/settings.py` and edit the relevant settings. -5. Create database: `./manage.py database create_tables`. -6. Start the web server: `./manage.py runserver`. -7. Start the worker: `./manage.py runworker`. -8. Open `http://localhost:5000/` and query away. +* [Setting up re:dash on Heroku in 5 minutes](https://github.com/EverythingMe/redash/wiki/Setting-up-re:dash-on-Heroku-in-5-minutes) +* [Setting re:dash on your own server (Ubuntu)](https://github.com/EverythingMe/redash/wiki/Setting-re:dash-on-your-own-server-(Ubuntu)) **Need help setting re:dash or one of the dependencies up?** Ping @arikfr on the IRC #redash channel or send a message to the [mailing list](https://groups.google.com/forum/#!forum/redash-users), and he will gladly help. diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 415006f58..000000000 --- a/Vagrantfile +++ /dev/null @@ -1,60 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = '2' - -POSTGRES_PASSWORD = 'securepass' - -# Currently, chef postgress cookbook works with cleartext paswords, -# unless the password begins with 'md5' -# See https://github.com/hw-cookbooks/postgresql/issues/95 -require "digest/md5" -postgres_password_md5 = 'md5'+Digest::MD5.hexdigest(POSTGRES_PASSWORD+'postgres') - -# After starting the vagrant machine, the application is accessible via the URL -# http://localhost:9999 -HOST_PORT_TO_FORWARD_TO_REDASH = 9999 - -# Deploy direcly the code in parent dir; Don't download a release tarball -live_testing_deployment = true - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = 'ubuntu-precise-cloudimg-amd64' - config.vm.box_url = 'http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box' - - if config.respond_to? :cache - config.cache.auto_detect = true - end - - config.berkshelf.enabled = true - config.omnibus.chef_version = :latest - - config.vm.network 'forwarded_port', guest: 8888, host: HOST_PORT_TO_FORWARD_TO_REDASH - - if live_testing_deployment - config.vm.synced_folder "..", "/opt/redash" - end - - config.vm.provision :chef_solo do |chef| - # run apt-get update before anything else (specifically postgresql).. - chef.add_recipe 'apt' - chef.add_recipe 'redash::redis_for_redash' - chef.add_recipe 'postgresql::client' - chef.add_recipe 'postgresql::server' - chef.add_recipe 'redash::redash_pg_schema' - chef.add_recipe 'redash::redash' - # chef.log_level = :debug - chef.json = { - 'apt' => { 'compiletime' => true }, - 'postgresql' => { 'password' => {'postgres' => postgres_password_md5 } }, - 'redash' => { 'db' => {'host' => 'localhost', - 'user' => 'postgres', - 'password' => POSTGRES_PASSWORD }, - 'allow' => {'google_app_domain' => 'gmail.com', - 'admins' => ['joe@egmail.com','jack@gmail.com']}, - 'install_tarball' => !live_testing_deployment, - 'user' => live_testing_deployment ? 'vagrant' : 'redash'} - } - end -end diff --git a/rd_ui/app/scripts/controllers.js b/rd_ui/app/scripts/controllers.js index 585764852..46eaa2e8c 100644 --- a/rd_ui/app/scripts/controllers.js +++ b/rd_ui/app/scripts/controllers.js @@ -1,8 +1,46 @@ (function () { - var DashboardCtrl = function ($scope, $routeParams, $http, Dashboard) { + var DashboardCtrl = function ($scope, $routeParams, $http, $timeout, Dashboard) { + $scope.refreshEnabled = false; + $scope.refreshRate = 60; $scope.dashboard = Dashboard.get({slug: $routeParams.dashboardSlug}, function(dashboard) { $scope.$parent.pageTitle = dashboard.name; }); + + var autoRefresh = function() { + if ($scope.refreshEnabled) { + $timeout(function() { + Dashboard.get({slug: $routeParams.dashboardSlug}, function(dashboard) { + var newWidgets = _.groupBy(_.flatten(dashboard.widgets), 'id'); + + _.each($scope.dashboard.widgets, function(row) { + _.each(row, function(widget, i) { + var newWidget = newWidgets[widget.id]; + if (newWidget && newWidget[0].visualization.query.latest_query_data_id != widget.visualization.query.latest_query_data_id ) { + row[i] = newWidget[0]; + } + }); + }); + + autoRefresh(); + }); + + }, $scope.refreshRate); + }; + } + + $scope.triggerRefresh = function(){ + $scope.refreshEnabled = !$scope.refreshEnabled; + + if ($scope.refreshEnabled) { + var refreshRate = _.min(_.flatten($scope.dashboard.widgets), function(widget) { + return widget.visualization.query.ttl; + }).visualization.query.ttl; + + $scope.refreshRate = _.max([120, refreshRate * 2])*1000; + + autoRefresh(); + } + }; }; var WidgetCtrl = function ($scope, $http, $location, Query) { @@ -148,6 +186,7 @@ $scope.refreshOptions = [ {value: -1, name: 'No Refresh'}, + {value: 60, name: 'Every minute'}, ] _.each(_.range(1, 13), function(i) { @@ -395,7 +434,7 @@ } angular.module('redash.controllers', []) - .controller('DashboardCtrl', ['$scope', '$routeParams', '$http', 'Dashboard', DashboardCtrl]) + .controller('DashboardCtrl', ['$scope', '$routeParams', '$http', '$timeout', 'Dashboard', DashboardCtrl]) .controller('WidgetCtrl', ['$scope', '$http', '$location', 'Query', WidgetCtrl]) .controller('QueriesCtrl', ['$scope', '$http', '$location', '$filter', 'Query', QueriesCtrl]) .controller('QueryFiddleCtrl', ['$scope', '$window', '$location', '$routeParams', '$http', '$location', 'growl', 'notifications', 'Query', 'Visualization', QueryFiddleCtrl]) diff --git a/rd_ui/app/styles/redash.css b/rd_ui/app/styles/redash.css index 2da630180..7a0b72841 100644 --- a/rd_ui/app/styles/redash.css +++ b/rd_ui/app/styles/redash.css @@ -213,4 +213,8 @@ to add those CSS styles here. */ color: white; background-color: #FF8080; border-radius: 50%; +} + +visualization-renderer > div { + overflow: scroll; } \ No newline at end of file diff --git a/rd_ui/app/views/dashboard.html b/rd_ui/app/views/dashboard.html index bfd5841d1..9ee76f7f7 100644 --- a/rd_ui/app/views/dashboard.html +++ b/rd_ui/app/views/dashboard.html @@ -4,6 +4,8 @@

{{dashboard.name}} + + diff --git a/redash/__init__.py b/redash/__init__.py index 6309e01ff..28bbe7dd7 100644 --- a/redash/__init__.py +++ b/redash/__init__.py @@ -33,7 +33,11 @@ def json_representation(data, code, headers=None): redis_url = urlparse.urlparse(settings.REDIS_URL) -redis_connection = redis.StrictRedis(host=redis_url.hostname, port=redis_url.port, db=0, password=redis_url.password) +if redis_url.path: + redis_db = redis_url.path[1] +else: + redis_db = 0 +redis_connection = redis.StrictRedis(host=redis_url.hostname, port=redis_url.port, db=redis_db, password=redis_url.password) from redash import data data_manager = data.Manager(redis_connection, db) diff --git a/redash/data/manager.py b/redash/data/manager.py index d787f2e1c..5061dff0f 100644 --- a/redash/data/manager.py +++ b/redash/data/manager.py @@ -158,7 +158,12 @@ class Manager(object): runner = query_runner_mysql.mysql(connection_string) elif connection_type == 'graphite': from redash.data import query_runner_graphite - runner = query_runner_graphite.graphite(connection_string) + connection_params = json.loads(connection_string) + if connection_params['auth']: + connection_params['auth'] = tuple(connection_params['auth']) + else: + connection_params['auth'] = None + runner = query_runner_graphite.graphite(connection_params) else: from redash.data import query_runner runner = query_runner.redshift(connection_string) diff --git a/redash/data/query_runner_mysql.py b/redash/data/query_runner_mysql.py index de9b7d7bf..a479322e9 100644 --- a/redash/data/query_runner_mysql.py +++ b/redash/data/query_runner_mysql.py @@ -43,7 +43,10 @@ def mysql(connection_string): cursor.close() except MySQLdb.Error, e: json_data = None - error = e.message + error = e.args[1] + except KeyboardInterrupt: + error = "Query cancelled by user." + json_data = None except Exception as e: raise sys.exc_info()[1], None, sys.exc_info()[2] finally: diff --git a/redash/settings.py b/redash/settings.py index f5df483b6..c20032851 100644 --- a/redash/settings.py +++ b/redash/settings.py @@ -39,7 +39,7 @@ CONNECTION_ADAPTER = os.environ.get("REDASH_CONNECTION_ADAPTER", "pg") # Connection string for the database that is used to run queries against. Examples: # -- mysql: CONNECTION_STRING = "Server=;User=;Pwd=;Database=" # -- pg: CONNECTION_STRING = "user= password= host= port=5439 dbname=" -# -- graphite: CONNECTION_STRING = {'url': 'https://graphite.yourcompany.com', 'auth': ('user', 'password'), 'verify': True} +# -- graphite: CONNECTION_STRING = {"url": "https://graphite.yourcompany.com", "auth": ["user", "password"], "verify": true} CONNECTION_STRING = os.environ.get("REDASH_CONNECTION_STRING", "user= password= host= port=5439 dbname=") # Connection settings for re:dash's own database (where we store the queries, results, etc)