Load currentUser/clientConfig from server

This commit is contained in:
Arik Fraimovich 2016-11-23 17:58:00 +02:00
parent 8676eb000d
commit 4ef4f98a66
42 changed files with 200 additions and 159 deletions

View File

@ -0,0 +1,8 @@
<div id="footer">
<a href="http://redash.io">Redash</a> <span ng-bind="$ctrl.version"></span> <small ng-if="$ctrl.newVersionAvailable" ng-cloak class="ng-cloak"><a href="https://version.redash.io/">(New Redash version available)</a></small>
<ul class="f-menu">
<li><a href="https://redash.io/help/">Documentation</a></li>
<li><a href="http://github.com/getredash/redash">Contribute</a></li>
</ul>
</div>

View File

@ -1,10 +1,13 @@
function controller() { import template from './footer.html';
function controller(clientConfig, currentUser) {
this.version = clientConfig.version;
this.newVersionAvailable = clientConfig.newVersionAvailable && currentUser.isAdmin;
} }
export default function (ngModule) { export default function (ngModule) {
ngModule.component('footer', { ngModule.component('footer', {
template: '<div>Footer</div>', template,
controller, controller,
}); });
} }

View File

@ -16,3 +16,4 @@ export { default as dynamicForm } from './dynamic-form';
export { default as rdTimer } from './rd-timer'; export { default as rdTimer } from './rd-timer';
export { default as rdTimeAgo } from './rd-time-ago'; export { default as rdTimeAgo } from './rd-time-ago';
export { default as overlay } from './overlay'; export { default as overlay } from './overlay';
export { default as routeStatus } from './route-status';

View File

@ -0,0 +1,19 @@
export default function (ngModule) {
ngModule.component('routeStatus', {
template: '<overlay ng-if="$ctrl.permissionDenied">You do not have permission to load this page.',
controller($rootScope) {
this.permissionDenied = false;
$rootScope.$on('$routeChangeSuccess', () => {
this.permissionDenied = false;
});
$rootScope.$on('$routeChangeError', (event, current, previous, rejection) => {
if (rejection.status === 403) {
this.permissionDenied = true;
}
});
},
});
}

View File

@ -12,7 +12,10 @@
<body ng-app="app"> <body ng-app="app">
<section> <section>
<app-header></app-header> <app-header></app-header>
<route-status></route-status>
<div ng-view></div> <div ng-view></div>
<footer>
</footer>
</section> </section>
</body> </body>
</html> </html>

View File

@ -46,47 +46,6 @@ const requirements = [
const ngModule = angular.module('app', requirements); const ngModule = angular.module('app', requirements);
// stub for currentUser until we have something real.
const user = {
name: 'Arik Fraimovich',
gravatar_url: 'https://www.gravatar.com/avatar/ca410c2e27337c8d7075bb1b098ac70f?s=40',
id: 1,
groups: [
3,
1,
],
email: 'arik@redash.io',
permissions: [
'admin',
'super_admin',
'create_dashboard',
'create_query',
'edit_dashboard',
'edit_query',
'view_query',
'view_source',
'list_users',
'execute_query',
'schedule_query',
'list_dashboards',
'list_alerts',
'create_alerts',
'list_dashboards',
'list_alerts',
'list_data_sources',
],
isAdmin: true,
};
user.hasPermission = () => true;
user.canEdit = () => true;
ngModule.constant('currentUser', user);
ngModule.constant('clientConfig', { // TODO: make me a service.
showPermissionsControl: true,
allowCustomJSVisualizations: true,
// mailSettingsMissing: true,
});
function registerComponents() { function registerComponents() {
each(components, (register) => { each(components, (register) => {
register(ngModule); register(ngModule);
@ -103,9 +62,14 @@ function registerPages() {
each(pages, (registerPage) => { each(pages, (registerPage) => {
const routes = registerPage(ngModule); const routes = registerPage(ngModule);
function session(Auth) {
return Auth.loadSession();
}
ngModule.config(($routeProvider) => { ngModule.config(($routeProvider) => {
each(routes, (route, path) => { each(routes, (route, path) => {
logger('Route: ', path); logger('Route: ', path);
route.resolve = Object.assign(route.resolve || {}, { session });
$routeProvider.when(path, route); $routeProvider.when(path, route);
}); });
}); });
@ -146,4 +110,10 @@ ngModule.config(($routeProvider,
}); });
}); });
ngModule.run(($location, Auth) => {
if (!Auth.isAuthenticated()) {
Auth.login();
}
});
export default ngModule; export default ngModule;

View File

@ -2,7 +2,7 @@ import moment from 'moment';
import template from './outdated-queries.html'; import template from './outdated-queries.html';
function OutdatedQueriesCtrl($scope, NgTableParams, currentUser, Events, $http, $timeout) { function OutdatedQueriesCtrl($scope, NgTableParams, currentUser, Events, $http, $timeout) {
Events.record(currentUser, 'view', 'page', 'admin/outdated_queries'); Events.record('view', 'page', 'admin/outdated_queries');
// $scope.$parent.pageTitle = 'Outdated Queries'; // $scope.$parent.pageTitle = 'Outdated Queries';
$scope.autoUpdate = true; $scope.autoUpdate = true;

View File

@ -2,7 +2,7 @@ import template from './status.html';
// TODO: switch to $ctrl instead of $scope. // TODO: switch to $ctrl instead of $scope.
function AdminStatusCtrl($scope, $http, $timeout, currentUser, Events) { function AdminStatusCtrl($scope, $http, $timeout, currentUser, Events) {
Events.record(currentUser, 'view', 'page', 'admin/status'); Events.record('view', 'page', 'admin/status');
// $scope.$parent.pageTitle = 'System Status'; // $scope.$parent.pageTitle = 'System Status';
const refresh = () => { const refresh = () => {

View File

@ -20,7 +20,7 @@ function cancelQueryButton() {
queryId = null; queryId = null;
} }
Events.record(currentUser, 'cancel_execute', 'query', queryId, { admin: true }); Events.record('cancel_execute', 'query', queryId, { admin: true });
$scope.inProgress = true; $scope.inProgress = true;
}; };
}, },

View File

@ -3,7 +3,7 @@ import template from './tasks.html';
import registerCancelQueryButton from './cancel-query-button'; import registerCancelQueryButton from './cancel-query-button';
function TasksCtrl($scope, $location, $http, $timeout, NgTableParams, currentUser, Events) { function TasksCtrl($scope, $location, $http, $timeout, NgTableParams, currentUser, Events) {
Events.record(currentUser, 'view', 'page', 'admin/tasks'); Events.record('view', 'page', 'admin/tasks');
// $scope.$parent.pageTitle = 'Running Queries'; // $scope.$parent.pageTitle = 'Running Queries';
$scope.autoUpdate = true; $scope.autoUpdate = true;

View File

@ -8,9 +8,9 @@ function AlertCtrl($routeParams, $location, $sce, toastr, currentUser, Query, Ev
this.alertId = $routeParams.alertId; this.alertId = $routeParams.alertId;
if (this.alertId === 'new') { if (this.alertId === 'new') {
Events.record(currentUser, 'view', 'page', 'alerts/new'); Events.record('view', 'page', 'alerts/new');
} else { } else {
Events.record(currentUser, 'view', 'alert', this.alertId); Events.record('view', 'alert', this.alertId);
} }
this.trustAsHtml = html => $sce.trustAsHtml(html); this.trustAsHtml = html => $sce.trustAsHtml(html);

View File

@ -2,7 +2,7 @@ import template from './alerts-list.html';
class AlertsListCtrl { class AlertsListCtrl {
constructor(NgTableParams, currentUser, Events, Alert) { constructor(NgTableParams, currentUser, Events, Alert) {
Events.record(currentUser, 'view', 'page', 'alerts'); Events.record('view', 'page', 'alerts');
// $scope.$parent.pageTitle = "Alerts"; // $scope.$parent.pageTitle = "Alerts";
this.tableParams = new NgTableParams({ count: 50 }, {}); this.tableParams = new NgTableParams({ count: 50 }, {});

View File

@ -82,7 +82,7 @@ function DashboardCtrl($routeParams, $location, $timeout, $q, $uibModal,
this.loadDashboard = _.throttle((force) => { this.loadDashboard = _.throttle((force) => {
this.dashboard = Dashboard.get({ slug: $routeParams.dashboardSlug }, (dashboard) => { this.dashboard = Dashboard.get({ slug: $routeParams.dashboardSlug }, (dashboard) => {
Events.record(currentUser, 'view', 'dashboard', dashboard.id); Events.record('view', 'dashboard', dashboard.id);
renderDashboard(dashboard, force); renderDashboard(dashboard, force);
}, () => { }, () => {
// error... // error...
@ -104,7 +104,7 @@ function DashboardCtrl($routeParams, $location, $timeout, $q, $uibModal,
this.archiveDashboard = () => { this.archiveDashboard = () => {
const archive = () => { const archive = () => {
Events.record(currentUser, 'archive', 'dashboard', this.dashboard.id); Events.record('archive', 'dashboard', this.dashboard.id);
this.dashboard.$delete(() => { this.dashboard.$delete(() => {
// TODO: // TODO:
// this.$parent.reloadDashboards(); // this.$parent.reloadDashboards();

View File

@ -79,7 +79,7 @@ const EditDashboardDialog = {
'Please copy/backup your changes and reload this page.', { autoDismiss: false }); 'Please copy/backup your changes and reload this page.', { autoDismiss: false });
} }
}); });
Events.record(currentUser, 'edit', 'dashboard', this.dashboard.id); Events.record('edit', 'dashboard', this.dashboard.id);
} else { } else {
$http.post('api/dashboards', { $http.post('api/dashboards', {
name: this.dashboard.name, name: this.dashboard.name,
@ -87,7 +87,7 @@ const EditDashboardDialog = {
this.close(); this.close();
$location.path(`/dashboard/${response.slug}`).replace(); $location.path(`/dashboard/${response.slug}`).replace();
}); });
Events.record(currentUser, 'create', 'dashboard'); Events.record('create', 'dashboard');
} }
}; };
}, },

View File

@ -41,7 +41,7 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
return; return;
} }
Events.record(currentUser, 'delete', 'widget', this.widget.id); Events.record('delete', 'widget', this.widget.id);
this.widget.$delete((response) => { this.widget.$delete((response) => {
this.dashboard.widgets = this.dashboard.widgets =
@ -54,7 +54,7 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
}); });
}; };
Events.record(currentUser, 'view', 'widget', this.widget.id); Events.record('view', 'widget', this.widget.id);
this.reload = (force) => { this.reload = (force) => {
let maxAge = $location.search().maxAge; let maxAge = $location.search().maxAge;
@ -65,8 +65,8 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
}; };
if (this.widget.visualization) { if (this.widget.visualization) {
Events.record(currentUser, 'view', 'query', this.widget.visualization.query.id); Events.record('view', 'query', this.widget.visualization.query.id);
Events.record(currentUser, 'view', 'visualization', this.widget.visualization.id); Events.record('view', 'visualization', this.widget.visualization.id);
this.query = this.widget.getQuery(); this.query = this.widget.getQuery();
this.reload(false); this.reload(false);

View File

@ -1,7 +1,7 @@
import template from './list.html'; import template from './list.html';
function DataSourcesCtrl($scope, $location, currentUser, Events, DataSource) { function DataSourcesCtrl($scope, $location, currentUser, Events, DataSource) {
Events.record(currentUser, 'view', 'page', 'admin/data_sources'); Events.record('view', 'page', 'admin/data_sources');
$scope.$parent.pageTitle = 'Data Sources'; $scope.$parent.pageTitle = 'Data Sources';
$scope.dataSources = DataSource.query(); $scope.dataSources = DataSource.query();

View File

@ -5,7 +5,7 @@ const logger = debug('redash:http');
function DataSourceCtrl($scope, $routeParams, $http, $location, toastr, function DataSourceCtrl($scope, $routeParams, $http, $location, toastr,
currentUser, Events, DataSource) { currentUser, Events, DataSource) {
Events.record(currentUser, 'view', 'page', 'admin/data_source'); Events.record('view', 'page', 'admin/data_source');
// $scope.$parent.pageTitle = 'Data Sources'; // $scope.$parent.pageTitle = 'Data Sources';
$scope.dataSourceId = $routeParams.dataSourceId; $scope.dataSourceId = $routeParams.dataSourceId;
@ -23,7 +23,7 @@ function DataSourceCtrl($scope, $routeParams, $http, $location, toastr,
}); });
function deleteDataSource() { function deleteDataSource() {
Events.record(currentUser, 'delete', 'datasource', $scope.dataSource.id); Events.record('delete', 'datasource', $scope.dataSource.id);
$scope.dataSource.$delete(() => { $scope.dataSource.$delete(() => {
toastr.success('Data source deleted successfully.'); toastr.success('Data source deleted successfully.');
@ -35,7 +35,7 @@ function DataSourceCtrl($scope, $routeParams, $http, $location, toastr,
} }
function testConnection(callback) { function testConnection(callback) {
Events.record(currentUser, 'test', 'datasource', $scope.dataSource.id); Events.record('test', 'datasource', $scope.dataSource.id);
DataSource.test({ id: $scope.dataSource.id }, (httpResponse) => { DataSource.test({ id: $scope.dataSource.id }, (httpResponse) => {
if (httpResponse.ok) { if (httpResponse.ok) {

View File

@ -1,7 +1,7 @@
import template from './list.html'; import template from './list.html';
function DestinationsCtrl($scope, $location, toastr, currentUser, Events, Destination) { function DestinationsCtrl($scope, $location, toastr, currentUser, Events, Destination) {
Events.record(currentUser, 'view', 'page', 'admin/destinations'); Events.record('view', 'page', 'admin/destinations');
// $scope.$parent.pageTitle = 'Destinations'; // $scope.$parent.pageTitle = 'Destinations';
$scope.destinations = Destination.query(); $scope.destinations = Destination.query();

View File

@ -6,7 +6,7 @@ const logger = debug('redash:http');
function DestinationCtrl($scope, $routeParams, $http, $location, toastr, function DestinationCtrl($scope, $routeParams, $http, $location, toastr,
currentUser, Events, Destination) { currentUser, Events, Destination) {
Events.record(currentUser, 'view', 'page', 'admin/destination'); Events.record('view', 'page', 'admin/destination');
$scope.$parent.pageTitle = 'Destinations'; $scope.$parent.pageTitle = 'Destinations';
$scope.destinationId = $routeParams.destinationId; $scope.destinationId = $routeParams.destinationId;
@ -24,7 +24,7 @@ function DestinationCtrl($scope, $routeParams, $http, $location, toastr,
}); });
$scope.delete = () => { $scope.delete = () => {
Events.record(currentUser, 'delete', 'destination', $scope.destination.id); Events.record('delete', 'destination', $scope.destination.id);
$scope.destination.$delete(() => { $scope.destination.$delete(() => {
toastr.success('Destination deleted successfully.'); toastr.success('Destination deleted successfully.');

View File

@ -3,7 +3,7 @@ import template from './data-sources.html';
function GroupDataSourcesCtrl($scope, $routeParams, $http, $location, toastr, function GroupDataSourcesCtrl($scope, $routeParams, $http, $location, toastr,
currentUser, Events, Group, DataSource) { currentUser, Events, Group, DataSource) {
Events.record(currentUser, 'view', 'group_data_sources', $scope.groupId); Events.record('view', 'group_data_sources', $scope.groupId);
$scope.group = Group.get({ id: $routeParams.groupId }); $scope.group = Group.get({ id: $routeParams.groupId });
$scope.dataSources = Group.dataSources({ id: $routeParams.groupId }); $scope.dataSources = Group.dataSources({ id: $routeParams.groupId });
$scope.newDataSource = {}; $scope.newDataSource = {};

View File

@ -2,7 +2,7 @@ import { Paginator } from '../../utils';
import template from './list.html'; import template from './list.html';
function GroupsCtrl($scope, $location, $uibModal, toastr, currentUser, Events, Group) { function GroupsCtrl($scope, $location, $uibModal, toastr, currentUser, Events, Group) {
Events.record(currentUser, 'view', 'page', 'groups'); Events.record('view', 'page', 'groups');
// $scope.$parent.pageTitle = 'Groups'; // $scope.$parent.pageTitle = 'Groups';
$scope.currentUser = currentUser; $scope.currentUser = currentUser;

View File

@ -3,7 +3,7 @@ import template from './show.html';
function GroupCtrl($scope, $routeParams, $http, $location, toastr, function GroupCtrl($scope, $routeParams, $http, $location, toastr,
currentUser, Events, Group, User) { currentUser, Events, Group, User) {
Events.record(currentUser, 'view', 'group', $scope.groupId); Events.record('view', 'group', $scope.groupId);
$scope.currentUser = currentUser; $scope.currentUser = currentUser;
$scope.group = Group.get({ id: $routeParams.groupId }); $scope.group = Group.get({ id: $routeParams.groupId });

View File

@ -1,7 +1,7 @@
import template from './home.html'; import template from './home.html';
function HomeCtrl($scope, $uibModal, currentUser, Events, Dashboard, Query) { function HomeCtrl($scope, $uibModal, currentUser, Events, Dashboard, Query) {
Events.record(currentUser, 'view', 'page', 'personal_homepage'); Events.record('view', 'page', 'personal_homepage');
// $scope.$parent.pageTitle = 'Home'; // $scope.$parent.pageTitle = 'Home';
// todo: maybe this should come from some serivce as we have this logic elsewhere. // todo: maybe this should come from some serivce as we have this logic elsewhere.

View File

@ -27,7 +27,7 @@ function QuerySearchCtrl($location, $filter, currentUser, Events, Query) {
} }
}; };
Events.record(currentUser, 'search', 'query', '', { term: this.term }); Events.record('search', 'query', '', { term: this.term });
} }
export default function (ngModule) { export default function (ngModule) {

View File

@ -9,7 +9,7 @@ function QuerySourceCtrl(Events, toastr, $controller, $scope, $location, $http,
// Obviously it shouldn't be repeated, but we got bigger fish to fry. // Obviously it shouldn't be repeated, but we got bigger fish to fry.
const DEFAULT_TAB = 'table'; const DEFAULT_TAB = 'table';
Events.record(currentUser, 'view_source', 'query', $scope.query.id); Events.record('view_source', 'query', $scope.query.id);
const isNewQuery = !$scope.query.id; const isNewQuery = !$scope.query.id;
let queryText = $scope.query.query; let queryText = $scope.query.query;
@ -74,7 +74,7 @@ function QuerySourceCtrl(Events, toastr, $controller, $scope, $location, $http,
}; };
$scope.duplicateQuery = () => { $scope.duplicateQuery = () => {
Events.record(currentUser, 'fork', 'query', $scope.query.id); Events.record('fork', 'query', $scope.query.id);
$scope.query.name = `Copy of (#${$scope.query.id}) ${$scope.query.name}`; $scope.query.name = `Copy of (#${$scope.query.id}) ${$scope.query.name}`;
$scope.query.id = null; $scope.query.id = null;
$scope.query.schedule = null; $scope.query.schedule = null;
@ -95,7 +95,7 @@ function QuerySourceCtrl(Events, toastr, $controller, $scope, $location, $http,
const confirm = { class: 'btn-danger', title: 'Delete' }; const confirm = { class: 'btn-danger', title: 'Delete' };
AlertDialog.open(title, message, confirm).then(() => { AlertDialog.open(title, message, confirm).then(() => {
Events.record(currentUser, 'delete', 'visualization', vis.id); Events.record('delete', 'visualization', vis.id);
Visualization.delete(vis, () => { Visualization.delete(vis, () => {
if ($scope.selectedTab === vis.id) { if ($scope.selectedTab === vis.id) {

View File

@ -90,7 +90,7 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $http, $location, $
$scope.showPermissionsControl = clientConfig.showPermissionsControl; $scope.showPermissionsControl = clientConfig.showPermissionsControl;
Events.record(currentUser, 'view', 'query', $scope.query.id); Events.record('view', 'query', $scope.query.id);
if ($scope.query.hasResult() || $scope.query.paramsRequired()) { if ($scope.query.hasResult() || $scope.query.paramsRequired()) {
getQueryResult(); getQueryResult();
} }
@ -156,12 +156,12 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $http, $location, $
}; };
$scope.saveDescription = () => { $scope.saveDescription = () => {
Events.record(currentUser, 'edit_description', 'query', $scope.query.id); Events.record('edit_description', 'query', $scope.query.id);
$scope.saveQuery(undefined, { description: $scope.query.description }); $scope.saveQuery(undefined, { description: $scope.query.description });
}; };
$scope.saveName = () => { $scope.saveName = () => {
Events.record(currentUser, 'edit_name', 'query', $scope.query.id); Events.record('edit_name', 'query', $scope.query.id);
$scope.saveQuery(undefined, { name: $scope.query.name }); $scope.saveQuery(undefined, { name: $scope.query.name });
}; };
@ -177,7 +177,7 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $http, $location, $
getQueryResult(0); getQueryResult(0);
$scope.lockButton(true); $scope.lockButton(true);
$scope.cancelling = false; $scope.cancelling = false;
Events.record(currentUser, 'execute', 'query', $scope.query.id); Events.record('execute', 'query', $scope.query.id);
Notifications.getPermissions(); Notifications.getPermissions();
}; };
@ -185,7 +185,7 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $http, $location, $
$scope.cancelExecution = () => { $scope.cancelExecution = () => {
$scope.cancelling = true; $scope.cancelling = true;
$scope.queryResult.cancelExecution(); $scope.queryResult.cancelExecution();
Events.record(currentUser, 'cancel_execute', 'query', $scope.query.id); Events.record('cancel_execute', 'query', $scope.query.id);
}; };
$scope.archiveQuery = () => { $scope.archiveQuery = () => {
@ -206,7 +206,7 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $http, $location, $
}; };
$scope.updateDataSource = () => { $scope.updateDataSource = () => {
Events.record(currentUser, 'update_data_source', 'query', $scope.query.id); Events.record('update_data_source', 'query', $scope.query.id);
localStorage.lastSelectedDataSourceId = $scope.query.data_source_id; localStorage.lastSelectedDataSourceId = $scope.query.data_source_id;
$scope.query.latest_query_data = null; $scope.query.latest_query_data = null;

View File

@ -4,7 +4,7 @@ import template from './edit.html';
function SnippetCtrl($routeParams, $http, $location, toastr, currentUser, Events, QuerySnippet) { function SnippetCtrl($routeParams, $http, $location, toastr, currentUser, Events, QuerySnippet) {
// $scope.$parent.pageTitle = 'Query Snippets'; // $scope.$parent.pageTitle = 'Query Snippets';
this.snippetId = $routeParams.snippetId; this.snippetId = $routeParams.snippetId;
Events.record(currentUser, 'view', 'query_snippet', this.snippetId); Events.record('view', 'query_snippet', this.snippetId);
this.editorOptions = { this.editorOptions = {
mode: 'snippets', mode: 'snippets',

View File

@ -2,7 +2,7 @@ import { Paginator } from '../../utils';
import template from './list.html'; import template from './list.html';
function SnippetsCtrl($location, currentUser, Events, QuerySnippet) { function SnippetsCtrl($location, currentUser, Events, QuerySnippet) {
Events.record(currentUser, 'view', 'page', 'query_snippets'); Events.record('view', 'page', 'query_snippets');
// $scope.$parent.pageTitle = 'Query Snippets'; // $scope.$parent.pageTitle = 'Query Snippets';
this.snippets = new Paginator([], { itemsPerPage: 20 }); this.snippets = new Paginator([], { itemsPerPage: 20 });

View File

@ -2,7 +2,7 @@ import { Paginator } from '../../utils';
import template from './list.html'; import template from './list.html';
function UsersCtrl($location, toastr, currentUser, Events, User) { function UsersCtrl($location, toastr, currentUser, Events, User) {
Events.record(currentUser, 'view', 'page', 'users'); Events.record('view', 'page', 'users');
// $scope.$parent.pageTitle = 'Users'; // $scope.$parent.pageTitle = 'Users';
this.currentUser = currentUser; this.currentUser = currentUser;

View File

@ -1,7 +1,7 @@
import template from './new.html'; import template from './new.html';
function NewUserCtrl($scope, $location, toastr, currentUser, Events, User) { function NewUserCtrl($scope, $location, toastr, currentUser, Events, User) {
Events.record(currentUser, 'view', 'page', 'users/new'); Events.record('view', 'page', 'users/new');
$scope.user = new User({}); $scope.user = new User({});
$scope.saveUser = () => { $scope.saveUser = () => {

View File

@ -13,7 +13,7 @@ function UserCtrl($scope, $routeParams, $http, $location, toastr,
$scope.userId = currentUser.id; $scope.userId = currentUser.id;
} }
Events.record(currentUser, 'view', 'user', $scope.userId); Events.record('view', 'user', $scope.userId);
$scope.canEdit = currentUser.hasPermission('admin') || currentUser.id === parseInt($scope.userId, 10); $scope.canEdit = currentUser.hasPermission('admin') || currentUser.id === parseInt($scope.userId, 10);
$scope.showSettings = false; $scope.showSettings = false;
$scope.showPasswordSettings = false; $scope.showPasswordSettings = false;

View File

@ -0,0 +1,56 @@
function getLocalSessionData() {
const sessionData = window.sessionStorage.getItem('session');
if (sessionData) {
return JSON.parse(sessionData);
}
return null;
}
function AuthService($window, $location, $q, $http) {
const Auth = {
isAuthenticated() {
return getLocalSessionData() !== null;
},
login() {
// const next = encodeURI($location.url());
console.log('do the login manually!');
// $window.location.href = `http://localhost:5000/default/login?next=${next}`;
},
loadSession() {
const sessionData = getLocalSessionData();
if (sessionData) {
return $q.resolve(sessionData);
}
return $http.get('/api/session').then((response) => {
window.sessionStorage.setItem('session', JSON.stringify(response.data));
return response.data;
});
},
};
return Auth;
}
function CurrentUserService() {
Object.assign(this, getLocalSessionData().user);
this.canEdit = (object) => {
const userId = object.user_id || (object.user && object.user.id);
return this.hasPermission('admin') || (userId && (userId === this.id));
};
this.hasPermission = permission => this.permissions.indexOf(permission) !== -1;
this.isAdmin = this.hasPermission('admin');
}
function ClientConfigService() {
Object.assign(this, getLocalSessionData().client_config);
}
export default function (ngModule) {
ngModule.factory('Auth', AuthService);
ngModule.service('currentUser', CurrentUserService);
ngModule.service('clientConfig', ClientConfigService);
}

View File

@ -10,9 +10,8 @@ function Events($http) {
$http.post('api/events', events); $http.post('api/events', events);
}, 1000); }, 1000);
this.record = function record(user, action, objectType, objectId, additionalProperties) { this.record = function record(action, objectType, objectId, additionalProperties) {
const event = { const event = {
user_id: user.id,
action, action,
object_type: objectType, object_type: objectType,
object_id: objectId, object_id: objectId,

View File

@ -13,3 +13,4 @@ export { default as QuerySnippet } from './query-snippet';
export { default as Notifications } from './notifications'; export { default as Notifications } from './notifications';
export { default as KeyboardShortcuts } from './keyboard-shortcuts'; export { default as KeyboardShortcuts } from './keyboard-shortcuts';
export { default as AlertDialog } from './alert-dialog'; export { default as AlertDialog } from './alert-dialog';
export { default as Auth } from './auth';

View File

@ -70,7 +70,7 @@ function Notifications(currentUser, Events) {
notification.onclick = function onClick() { notification.onclick = function onClick() {
window.focus(); window.focus();
this.close(); this.close();
Events.record(currentUser, 'click', 'notification'); Events.record('click', 'notification');
}; };
}; };

View File

@ -47,9 +47,9 @@ const EditVisualizationDialog = {
this.submit = () => { this.submit = () => {
if (this.visualization.id) { if (this.visualization.id) {
Events.record(currentUser, 'update', 'visualization', this.visualization.id, { type: this.visualization.type }); Events.record('update', 'visualization', this.visualization.id, { type: this.visualization.type });
} else { } else {
Events.record(currentUser, 'create', 'visualization', null, { type: this.visualization.type }); Events.record('create', 'visualization', null, { type: this.visualization.type });
} }
this.visualization.query_id = this.query.id; this.visualization.query_id = this.query.id;

View File

@ -11,7 +11,6 @@
<!-- build:css /styles/main.css --> <!-- build:css /styles/main.css -->
<link rel="stylesheet" href="/bower_components/pace/themes/pace-theme-minimal.css"> <link rel="stylesheet" href="/bower_components/pace/themes/pace-theme-minimal.css">
<link rel="stylesheet" href="/styles/redash.css">
<!-- endbuild --> <!-- endbuild -->
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
@ -72,20 +71,7 @@
<script> <script>
// TODO: move currentUser & features to be an Angular service // TODO: move currentUser & features to be an Angular service
var clientConfig = {{ client_config|safe }};
var basePath = "{{base_href}}"; var basePath = "{{base_href}}";
var currentUser = {{ user|safe }};
currentUser.canEdit = function(object) {
var user_id = object.user_id || (object.user && object.user.id);
return this.hasPermission('admin') || (user_id && (user_id == currentUser.id));
};
currentUser.hasPermission = function(permission) {
return this.permissions.indexOf(permission) != -1;
};
currentUser.isAdmin = currentUser.hasPermission('admin');
</script> </script>
{% include '_includes/tail.html' %} {% include '_includes/tail.html' %}

View File

@ -1,32 +0,0 @@
(function () {
var dateFormatter = function (value) {
if (!value) {
return "-";
}
return value.format(clientConfig.dateTimeFormat);
};
var MainCtrl = function ($scope, $location, Dashboard) {
$scope.$on("$routeChangeSuccess", function (event, current, previous, rejection) {
if ($scope.showPermissionError) {
$scope.showPermissionError = false;
}
});
$scope.$on("$routeChangeError", function (event, current, previous, rejection) {
if (rejection.status === 403) {
$scope.showPermissionError = true;
}
});
$scope.location = String(document.location);
$scope.version = clientConfig.version;
$scope.newVersionAvailable = clientConfig.newVersionAvailable && currentUser.hasPermission("admin");
};
angular.module('redash.controllers', [])
.controller('IndexCtrl', ['$scope', 'Events', 'Dashboard', 'Query', IndexCtrl])
.controller('MainCtrl', ['$scope', '$location', 'Dashboard', MainCtrl])
.controller('QuerySearchCtrl', ['$scope', '$location', '$filter', 'Events', 'Query', QuerySearchCtrl])
})();

View File

@ -1,18 +1,13 @@
import json import json
from flask import current_app
from flask_login import login_required
from flask_login import login_required
from redash import models, redis_connection from redash import models, redis_connection
from redash.utils import json_dumps
from redash.handlers import routes from redash.handlers import routes
from redash.handlers.base import json_response
from redash.permissions import require_super_admin from redash.permissions import require_super_admin
from redash.tasks.queries import QueryTaskTracker from redash.tasks.queries import QueryTaskTracker
def json_response(response):
return current_app.response_class(json_dumps(response), mimetype='application/json')
@routes.route('/api/admin/queries/outdated', methods=['GET']) @routes.route('/api/admin/queries/outdated', methods=['GET'])
@require_super_admin @require_super_admin
@login_required @login_required
@ -45,4 +40,3 @@ def queries_tasks():
} }
return json_response(response) return json_response(response)

View File

@ -1,12 +1,16 @@
import hashlib
import logging import logging
from flask import render_template, request, redirect, url_for, flash
from flask_login import current_user, login_user, logout_user
from redash import models, settings, limiter from flask import flash, redirect, render_template, request, url_for
from redash.handlers import routes from flask_login import current_user, login_user, logout_user
from redash.handlers.base import org_scoped_rule from redash import __version__, models, settings, limiter
from redash.authentication import current_org, get_login_url from redash.authentication import current_org, get_login_url
from redash.authentication.account import validate_token, BadSignature, SignatureExpired, send_password_reset_email from redash.authentication.account import (BadSignature, SignatureExpired,
send_password_reset_email,
validate_token)
from redash.handlers import routes
from redash.handlers.base import json_response, org_scoped_rule
from redash.version_check import get_latest_version
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -125,3 +129,31 @@ def login(org_slug=None):
def logout(org_slug=None): def logout(org_slug=None):
logout_user() logout_user()
return redirect(get_login_url(next=None)) return redirect(get_login_url(next=None))
@routes.route(org_scoped_rule('/api/session'), methods=['GET'])
def session(org_slug=None):
email_md5 = hashlib.md5(current_user.email.lower()).hexdigest()
gravatar_url = "https://www.gravatar.com/avatar/%s?s=40" % email_md5
user = {
'gravatar_url': gravatar_url,
'id': current_user.id,
'name': current_user.name,
'email': current_user.email,
'groups': current_user.groups,
'permissions': current_user.permissions
}
client_config = {
'newVersionAvailable': get_latest_version(),
'version': __version__
}
client_config.update(settings.COMMON_CLIENT_CONFIG)
return json_response({
'user': user,
'org_slug': current_org.slug,
'client_config': client_config
})

View File

@ -1,13 +1,14 @@
import time import time
from flask import request, Blueprint
from flask_restful import Resource, abort
from flask_login import current_user, login_required
from peewee import DoesNotExist
from flask import Blueprint, current_app, request
from flask_login import current_user, login_required
from flask_restful import Resource, abort
from peewee import DoesNotExist
from redash import settings from redash import settings
from redash.tasks import record_event as record_event_task
from redash.models import ApiUser
from redash.authentication import current_org from redash.authentication import current_org
from redash.models import ApiUser
from redash.tasks import record_event as record_event_task
from redash.utils import json_dumps
routes = Blueprint('redash', __name__, template_folder=settings.fix_assets_path('templates')) routes = Blueprint('redash', __name__, template_folder=settings.fix_assets_path('templates'))
@ -99,3 +100,7 @@ def org_scoped_rule(rule):
return "/<org_slug:org_slug>{}".format(rule) return "/<org_slug:org_slug>{}".format(rule)
return rule return rule
def json_response(response):
return current_app.response_class(json_dumps(response), mimetype='application/json')

View File

@ -117,10 +117,6 @@ class QueryResource(BaseResource):
except models.ConflictDetectedError: except models.ConflictDetectedError:
abort(409) abort(409)
# old_query = copy.deepcopy(query.to_dict())
# new_change = query.update_instance_tracked(changing_user=self.current_user, old_object=old_query, **query_def)
# abort(409) # HTTP 'Conflict' status code
result = query.to_dict(with_visualizations=True) result = query.to_dict(with_visualizations=True)
return result return result