Skip to content
Snippets Groups Projects
Commit bbda587f authored by Cam Saul's avatar Cam Saul
Browse files

OMG THIS IS SO NICE! Use the same code to edit DBs in setup and admin....

OMG THIS IS SO NICE! Use the same code to edit DBs in setup and admin. :heart_eyes_cat: :smiling_imp:
parent 20b33127
No related branches found
No related tags found
No related merge requests found
......@@ -44,143 +44,10 @@ DatabasesControllers.controller('DatabaseList', ['$scope', 'Metabase', function(
});
}]);
DatabasesControllers.controller('DatabaseEdit', ['$scope', '$routeParams', '$location', 'Metabase',
function($scope, $routeParams, $location, Metabase) {
DatabasesControllers.controller('DatabaseEdit', ['$scope', '$routeParams', '$location', 'Metabase', 'CorvusCore',
function($scope, $routeParams, $location, Metabase, CorvusCore) {
$scope.ENGINES = {
postgres: {
fields: [{
displayName: "Host",
fieldName: "host",
placeholder: "localhost",
required: true
}, {
displayName: "Port",
fieldName: "port",
placeholder: "5432",
required: true
}, {
displayName: "Database name",
fieldName: "dbname",
placeholder: "birds_of_the_world",
required: true
}, {
displayName: "Database username",
fieldName: "user",
placeholder: "What username do you use to login to the database?",
required: true
}, {
displayName: "Database password",
fieldName: "pass",
placeholder: "*******"
}, {
displayName: "Use a secure connection (SSL)?",
fieldName: "ssl",
choices: [{
name: 'Yes <3',
value: true,
selectionAccent: 'active'
}, {
name: 'No :/',
value: false,
selectionAccent: 'danger'
}]
}],
parseDetails: function(details) {
var map = {
ssl: details.ssl
};
details.conn_str.split(' ').forEach(function(val) {
var split = val.split('=');
if (split.length === 2) {
map[split[0]] = split[1];
}
});
return map;
},
buildDetails: function(details) {
var connStr = "host=" + details.host + " port=" + details.port + " dbname=" + details.dbname + " user=" + details.user;
if (details.pass) {
connStr += " password=" + details.pass;
}
return {
conn_str: connStr,
ssl: details.ssl
};
}
},
h2: {
fields: [{
displayName: "Connection String",
fieldName: "connectionString",
placeholder: "file:/Users/camsaul/bird_sightings/toucans;AUTO_SERVER=TRUE"
}],
parseDetails: function(details) {
return {
connectionString: details.conn_str
};
},
buildDetails: function(details) {
return {
conn_str: details.connectionString
};
}
},
mongo: {
fields: [{
displayName: "Host",
fieldName: "host",
placeholder: "localhost",
required: true
}, {
displayName: "Port",
fieldName: "port",
placeholder: "27017"
}, {
displayName: "Database name",
fieldName: "dbname",
placeholder: "carrierPigeonDeliveries",
required: true
}, {
displayName: "Database username",
fieldName: "user",
placeholder: "What username do you use to login to the database?"
}, {
displayName: "Database password",
fieldName: "pass",
placeholder: "******"
}],
parseDetails: function(details) {
var regex = /^mongodb:\/\/(?:([^@:]+)(?::([^@:]+))?@)?([^\/:@]+)(?::([\d]+))?\/([^\/]+)$/gm, // :scream:
matches = regex.exec(details.conn_str);
return {
user: matches[1],
pass: matches[2],
host: matches[3],
port: matches[4],
dbname: matches[5]
};
},
buildDetails: function(details) {
var connStr = "mongodb://";
if (details.user) {
connStr += details.user;
if (details.pass) {
connStr += ":" + details.pass;
}
connStr += "@";
}
connStr += details.host;
if (details.port) {
connStr += ":" + details.port;
}
connStr += "/" + details.dbname;
return {
conn_str: connStr
};
}
}
};
$scope.ENGINES = CorvusCore.ENGINES;
// update an existing Database
var update = function(database, details) {
......
......@@ -7,10 +7,22 @@
</section>
<section class="Grid Grid--gutters Grid--full">
<!-- form -->
<div ng-include="'/app/admin/databases/partials/database_edit_forms.html'" class="Grid-cell Cell--2of3">
<div class="Grid-cell Cell--2of3">
<form class="Form-new bordered rounded shadowed" name="form" novalidate>
<!-- Form -->
<div ng-include="'/app/admin/databases/partials/database_edit_forms.html'"></div>
<!-- Bottom Actions -->
<div class="Form-actions">
<button class="Button" ng-class="{'Button--primary': form.$valid}" ng-click="save(database, details)" ng-disabled="!form.$valid">
Save
</button>
<mb-form-message></mb-form-message>
</div>
</form>
</div>
<!-- actions -->
<!-- Sidebar Actions -->
<div class="Grid-cell Cell--1of3" ng-if="database.id">
<div class="Actions">
<h3>Actions</h3>
......
<!-- form -->
<div>
<form class="Form-new bordered rounded shadowed" name="form" novalidate>
<div class="Form-field" mb-form-field="engine">
<mb-form-label display-name="Database type" field-name="engine"></mb-form-label>
<label class="Select Form-offset">
<select name="engine" ng-model="database.engine" ng-options="type as properties.name for (type, properties) in form_input.engines"></select>
</label>
</div>
<!-- DB Engine (database.engine) -->
<div class="Form-field" mb-form-field="engine">
<mb-form-label display-name="Database type" field-name="engine"></mb-form-label>
<label class="Select Form-offset">
<select name="engine" ng-model="database.engine" ng-options="type as properties.name for (type, properties) in ENGINES"></select>
</label>
</div>
<div class="Form-field" mb-form-field="engine">
<mb-form-label display-name="Name" field-name="name"></mb-form-label>
<input class="Form-input Form-offset full" name="name" placeholder="How would you like to refer to this database?" ng-model="database.name" required autofocus />
<span class="Form-charm"></span>
</div>
<!-- DB Nickname (database.name) -->
<div class="Form-field" mb-form-field="engine">
<mb-form-label display-name="Name" field-name="name"></mb-form-label>
<input class="Form-input Form-offset full" name="name" placeholder="How would you like to refer to this database?" ng-model="database.name" required autofocus />
<span class="Form-charm"></span>
</div>
<!-- Database Input Fields - show a form for each field in DatabaseEdit.ENGINES[database.engine].fields -->
<div class="FormInputGroup">
<div class="Form-field" ng-repeat="field in ENGINES[database.engine].fields" mb-form-field="{{field.fieldName}}">
<mb-form-label display-name="{{field.displayName}}" field-name="{{field.fieldName}}"></mb-form-label>
<!-- Multiple-Choice Field -->
<div ng-if="field.choices" class="Form-input Form-offset full Button-group">
<button ng-repeat="choice in field.choices" class="Button"
ng-class="details[field.fieldName] === choice.value ? {active: 'Button--active',
danger: 'Button--danger'}[choice.selectionAccent] : null"
ng-click="details[field.fieldName] = choice.value">
{{choice.name}}
</button>
</div>
<!-- String Field (default) -->
<input ng-if="!field.choices" class="Form-input Form-offset full" name="{{field.fieldName}}" placeholder="{{field.placeholder}}"
ng-model="details[field.fieldName]" ng-required="field.required" />
<span class="Form-charm"></span>
<!-- Database Connection Info - Varies by Engine (details.*) -->
<div class="FormInputGroup">
<div class="Form-field" ng-repeat="field in ENGINES[database.engine].fields" mb-form-field="{{field.fieldName}}">
<mb-form-label display-name="{{field.displayName}}" field-name="{{field.fieldName}}"></mb-form-label>
<!-- Multiple-Choice Field -->
<div ng-if="field.choices" class="Form-input Form-offset full Button-group">
<button ng-repeat="choice in field.choices" class="Button"
ng-class="details[field.fieldName] === choice.value ? {active: 'Button--active',
danger: 'Button--danger'}[choice.selectionAccent] : null"
ng-click="details[field.fieldName] = choice.value">
{{choice.name}}
</button>
</div>
<!-- String Field (default) -->
<input ng-if="!field.choices" class="Form-input Form-offset full" name="{{field.fieldName}}" placeholder="{{field.placeholder}}"
ng-model="details[field.fieldName]" ng-required="field.required" />
<span class="Form-charm"></span>
</div>
<div class="Form-actions">
<button class="Button" ng-class="{'Button--primary': form.$valid}" ng-click="save(database, details)" ng-disabled="!form.$valid">
Save
</button>
<mb-form-message></mb-form-message>
</div>
</form>
</div>
</div>
......@@ -32,9 +32,9 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
initPromise = deferred.promise;
// just make sure we grab the current user
service.refreshCurrentUser().then(function (user) {
service.refreshCurrentUser().then(function(user) {
deferred.resolve();
}, function (error) {
}, function(error) {
deferred.resolve();
});
}
......@@ -63,7 +63,7 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
refreshCurrentUser: function() {
// this is meant to be called once on app startup
var userRefresh = User.current(function (result) {
var userRefresh = User.current(function(result) {
service.model.currentUser = result;
// add isMember(orgSlug) method to the object
......@@ -101,7 +101,7 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
$rootScope.$broadcast('appstate:user', result);
}, function (error) {
}, function(error) {
console.log('unable to get current user', error);
});
......@@ -167,9 +167,9 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
// this code is here to ensure that we have resolved our currentUser BEFORE we execute any other
// code meant to establish app context based on the current route
currentUserPromise.then(function (user) {
currentUserPromise.then(function(user) {
service.routeChangedImpl(event);
}, function (error) {
}, function(error) {
service.routeChangedImpl(event);
});
},
......@@ -446,6 +446,7 @@ CorvusServices.service('CorvusCore', ['$resource', 'User', function($resource, U
'id': 'zip_code',
'name': 'Zip Code'
}];
this.field_field_types = [{
'id': 'info',
'name': 'Information'
......@@ -519,6 +520,146 @@ CorvusServices.service('CorvusCore', ['$resource', 'User', function($resource, U
// this just makes it easier to access the current user
this.currentUser = User.current;
// The various DB engines we support <3
// TODO - this should probably come back from the API, no?
this.ENGINES = {
postgres: {
name: 'Postgres',
fields: [{
displayName: "Host",
fieldName: "host",
placeholder: "localhost",
required: true
}, {
displayName: "Port",
fieldName: "port",
placeholder: "5432",
required: true
}, {
displayName: "Database name",
fieldName: "dbname",
placeholder: "birds_of_the_world",
required: true
}, {
displayName: "Database username",
fieldName: "user",
placeholder: "What username do you use to login to the database?",
required: true
}, {
displayName: "Database password",
fieldName: "pass",
placeholder: "*******"
}, {
displayName: "Use a secure connection (SSL)?",
fieldName: "ssl",
choices: [{
name: 'Yes <3',
value: true,
selectionAccent: 'active'
}, {
name: 'No :/',
value: false,
selectionAccent: 'danger'
}]
}],
parseDetails: function(details) {
var map = {
ssl: details.ssl
};
details.conn_str.split(' ').forEach(function(val) {
var split = val.split('=');
if (split.length === 2) {
map[split[0]] = split[1];
}
});
return map;
},
buildDetails: function(details) {
var connStr = "host=" + details.host + " port=" + details.port + " dbname=" + details.dbname + " user=" + details.user;
if (details.pass) {
connStr += " password=" + details.pass;
}
return {
conn_str: connStr,
ssl: details.ssl
};
}
},
h2: {
name: 'H2',
fields: [{
displayName: "Connection String",
fieldName: "connectionString",
placeholder: "file:/Users/camsaul/bird_sightings/toucans;AUTO_SERVER=TRUE"
}],
parseDetails: function(details) {
return {
connectionString: details.conn_str
};
},
buildDetails: function(details) {
return {
conn_str: details.connectionString
};
}
},
mongo: {
name: 'MongoDB',
fields: [{
displayName: "Host",
fieldName: "host",
placeholder: "localhost",
required: true
}, {
displayName: "Port",
fieldName: "port",
placeholder: "27017"
}, {
displayName: "Database name",
fieldName: "dbname",
placeholder: "carrierPigeonDeliveries",
required: true
}, {
displayName: "Database username",
fieldName: "user",
placeholder: "What username do you use to login to the database?"
}, {
displayName: "Database password",
fieldName: "pass",
placeholder: "******"
}],
parseDetails: function(details) {
var regex = /^mongodb:\/\/(?:([^@:]+)(?::([^@:]+))?@)?([^\/:@]+)(?::([\d]+))?\/([^\/]+)$/gm, // :scream:
matches = regex.exec(details.conn_str);
return {
user: matches[1],
pass: matches[2],
host: matches[3],
port: matches[4],
dbname: matches[5]
};
},
buildDetails: function(details) {
var connStr = "mongodb://";
if (details.user) {
connStr += details.user;
if (details.pass) {
connStr += ":" + details.pass;
}
connStr += "@";
}
connStr += details.host;
if (details.port) {
connStr += ":" + details.port;
}
connStr += "/" + details.dbname;
return {
conn_str: connStr
};
}
}
};
}]);
CorvusServices.service('CorvusAlert', [function() {
......@@ -749,8 +890,8 @@ CoreServices.factory('User', ['$resource', '$cookies', function($resource, $cook
CoreServices.factory('Organization', ['$resource', '$cookies', function($resource, $cookies) {
return $resource('/api/org/:orgId', {}, {
form_input: {
url:'/api/org/form_input',
method:'GET'
url: '/api/org/form_input',
method: 'GET'
},
list: {
url: '/api/org/',
......@@ -887,4 +1028,4 @@ CoreServices.factory('PermissionViolation', ['$resource', '$cookies', function($
},
});
}]);
}]);
\ No newline at end of file
......@@ -11,38 +11,18 @@
</section>
<section class="SetupOffset">
<div class="SetupWrapper">
<div class="AdminContent AdminContent-shallow">
<div class="p4">
<div>
<ul class="Connection-enginePicker">
<li class="Connection-engine" ng-repeat="engine in engines" ng-click="setConnectionEngine(engine.id)" ng-class="{'Connection-engineSelected': connection.engine == engine.id }">
{{engine.name}}
</li>
</ul>
<div ng-show="connection.engine != undefined">
<input class="Connection-input" type="text" placeholder="Database name" ng-model="connection.dbname" autofocus>
<label class="Connection-label">Database name</label>
<input class="Connection-input" type="text" placeholder="Database Host" ng-model="connection.host">
<label class="Connection-label">Host</label>
<input class="Connection-input" type="text" placeholder="Port" ng-model="connection.port">
<label class="Connection-label">Database port</label>
<input class="Connection-input" type="text" placeholder="Username" ng-model="connection.user">
<label class="Connection-label">Database username</label>
<input class="Connection-input" type="password" placeholder="Password" ng-model="connection.password">
<label class="Connection-label">Database password</label>
<div class="AdminContent AdminContent-shallow">
<div class="p4">
<div>
<div ng-include="'/app/admin/databases/partials/database_edit_forms.html'"></div>
</div>
<div class="bg-error text-white p2 rounded mb2" ng-show="error">
{{error}}
</div>
</div>
<div class="p3 border-top clearfix">
<button class="Button Button--primary float-right" ng-click="submit()">Connect</button>
<a class="Button" href="/setup/data">Cancel</a>
</div>
</div>
<div class="bg-error text-white p2 rounded mb2" ng-show="error">
{{error}}
</div>
</div>
<div class="p3 border-top clearfix">
<button class="Button Button--primary float-right" ng-click="submit()">Connect</button>
<a class="Button" href="/setup/data">Cancel</a>
</div>
</div>
</section>
......@@ -77,30 +77,11 @@ SetupControllers.controller('SetupIntro', ['$scope', '$location', '$timeout', 'i
}
]);
SetupControllers.controller('SetupConnection', ['$scope', '$routeParams', '$location', 'Metabase', function($scope, $routeParams, $location, Metabase) {
SetupControllers.controller('SetupConnection', ['$scope', '$routeParams', '$location', 'Metabase', 'CorvusCore', function($scope, $routeParams, $location, Metabase, CorvusCore) {
// TODO - we should be getting all this info from the api
$scope.ENGINES = CorvusCore.ENGINES;
var defaultPorts = {
'MySql': 3306,
'Postgres': 5432,
'Mongo': 27017,
"RedShift": 5439,
'Druid': 8083
};
$scope.engines = [{
'id': 'postgres',
'name': 'Postgres'
}, {
'id': 'h2',
'name': 'H2'
}, {
'id': 'mysql',
'name': 'MySQL'
}];
$scope.connection = {};
$scope.details = {};
// assume we have a new connection since this is the setup process
var newConnection = true;
......@@ -113,62 +94,34 @@ SetupControllers.controller('SetupConnection', ['$scope', '$routeParams', '$loca
}, function(result) {
$scope.database = result;
$scope.breadcrumb = result.name;
$scope.connection = parseConnectionString(result.details.conn_str);
$scope.connection.engine = result.engine;
$scope.details = $scope.ENGINES[result.engine].parseDetails(result.details);
}, function(error) {
console.log('error', error);
});
} else {
var connectionType = 'Postgres';
$scope.connection = {
host: "localhost",
port: defaultPorts[connectionType],
engine: 'postgres'
$scope.details = {
host: 'localhost',
port: '5432',
ssl: false
};
$scope.database = {
engine: 'postgres',
details: $scope.details
};
}
function parseConnectionString(connectionString) {
// connection strings take the form of
// 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
var parsedConnection = {};
var string = connectionString.split(" ");
for (var s in string) {
var connectionDetail = string[s].split("=");
parsedConnection[connectionDetail[0]] = connectionDetail[1];
}
return parsedConnection;
}
function buildConnectionString(values) {
// connection strings take the form of
// 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
var connectionStringElements = ['host', 'port', 'dbname', 'user', 'password'],
conn_str = '';
for (var element in connectionStringElements) {
conn_str = conn_str + ' ' + connectionStringElements[element] + '=' + values[connectionStringElements[element]];
}
return conn_str;
}
$scope.setConnectionEngine = function(engine) {
$scope.connection.engine = engine;
$scope.database.engine = engine;
};
$scope.submit = function() {
var database = {
org: $scope.currentOrg.id,
name: $scope.connection.dbname,
engine: $scope.connection.engine,
details: {
conn_str: buildConnectionString($scope.connection)
}
};
var engine = $scope.database.engine,
database = {
org: $scope.currentOrg.id,
name: $scope.database.name,
engine: engine,
details: $scope.ENGINES[engine].buildDetails($scope.details)
};
function success(result) {
$location.path('/setup/data');
......@@ -180,9 +133,13 @@ SetupControllers.controller('SetupConnection', ['$scope', '$routeParams', '$loca
}
// api needs a int
$scope.connection.port = parseInt($scope.connection.port);
// Validate the connection string
Metabase.validate_connection($scope.connection, function(result) {
if ($scope.details.port) {
$scope.details.port = parseInt($scope.details.port);
}
// Validate the connection string. Add engine to the request body
$scope.details.engine = $scope.database.engine;
Metabase.validate_connection($scope.details, function(result) {
if (newConnection) {
Metabase.db_create(database, success, error);
} else {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment