提交 877d34cc 编写于 作者: S Sercan

fixes #9

上级 9d5d8d97
......@@ -22,9 +22,10 @@ zimme:active-route
okgrow:router-autoscroll
less
twbs:bootstrap@3.3.6
ephemer:reactive-datatables
meteorhacks:npm
npm-container
momentjs:moment
arch:ace-editor
aldeed:collection2
meteorhacks:kadira
ephemer:reactive-datatables-modified
......@@ -27,7 +27,8 @@ diff-sequence@1.0.1
ecmascript@0.1.6
ecmascript-runtime@0.2.6
ejson@1.0.7
ephemer:reactive-datatables@1.1.0
email@1.0.8
ephemer:reactive-datatables-modified@1.1.0
es5-shim@4.1.14
fastclick@1.0.7
fortawesome:fontawesome@4.5.0
......@@ -49,11 +50,14 @@ jquery@1.11.4
launch-screen@1.0.4
less@2.5.1
livedata@1.0.15
localstorage@1.0.5
logging@1.0.8
mdg:validation-error@0.2.0
meteor@1.1.10
meteor-base@1.0.1
meteorhacks:async@1.0.0
meteorhacks:kadira@2.27.2
meteorhacks:meteorx@1.4.1
meteorhacks:npm@1.5.0
minifiers@1.1.7
minimongo@1.0.10
......@@ -62,6 +66,7 @@ mobile-status-bar@1.0.6
momentjs:moment@2.10.6
mongo@1.1.3
mongo-id@1.0.1
mongo-livedata@1.0.9
npm-container@1.2.0
npm-mongo@1.4.39_1
observe-sequence@1.0.7
......
......@@ -10,13 +10,9 @@ Template.strSessionServerStatus = "serverStatus";
Template.strSessionDBStats = "dbStats";
Template.clearSessions = function () {
Session.set(Template.strSessionCollectionNames, undefined);
Session.set(Template.strSessionConnection, undefined);
Session.set(Template.strSessionSelectedCollection, undefined);
Session.set(Template.strSessionSelectedQuery, undefined);
Session.set(Template.strSessionSelectedOptions, undefined);
Session.set(Template.strSessionServerStatus, undefined);
Session.set(Template.strSessionDBStats, undefined);
Object.keys(Session.keys).forEach(function (key) {
Session.set(key, undefined);
})
};
Template.renderAfterQueryExecution = function (err, result) {
......
......@@ -46,7 +46,7 @@
</small>
</div>
<div class="modal-body">
{{> ReactiveDatatable tableData=reactiveDataFunction options=optionsObject }}
{{> ReactiveDatatable tableData=reactiveDataFunction options=optionsObject tableId="tblConnection" }}
<button id="btnCreateNewConnection" type="button" class="btn btn-block btn-outline btn-primary"
data-toggle="modal"
data-target="#connectionCreateModal">Create New
......
Template.topNavbar.rendered = function () {
var selector = $('#DataTables_Table_0');
var selector = $('#tblConnection');
selector.addClass('table-striped table-bordered table-hover');
selector.find('tbody').on('click', 'tr', function () {
......@@ -92,7 +92,7 @@ Template.topNavbar.events({
'click #btnConnectionList': function () {
if (!Session.get(Template.strSessionConnection)) {
$('#DataTables_Table_0').DataTable().$('tr.selected').removeClass('selected');
$('#tblConnection').DataTable().$('tr.selected').removeClass('selected');
$('#btnConnect').prop('disabled', true);
}
},
......@@ -100,7 +100,7 @@ Template.topNavbar.events({
'click .editor_remove': function (e) {
e.preventDefault();
// set rows not selected
$('#DataTables_Table_0').DataTable().$('tr.selected').removeClass('selected');
$('#tblConnection').DataTable().$('tr.selected').removeClass('selected');
// disable connect button
$('#btnConnect').prop('disabled', true);
// remove connection
......
......@@ -10,7 +10,7 @@
<h5>Dumps for {{getConnection.name}}</h5>
</div>
<div class="ibox-content">
{{> ReactiveDatatable tableData=getDumps options=dumpsTableOptions }}
{{> ReactiveDatatable tableData=getDumps options=dumpsTableOptions tableId="tblDumps" }}
</div>
<div class="ibox-footer">
<!--<span class="pull-right">
......
......@@ -4,7 +4,24 @@
Template.databaseDumpRestore.onRendered(function () {
if (Session.get(Template.strSessionCollectionNames) == undefined) {
Router.go('databaseStats');
return;
}
var selector = $('#tblDumps');
selector.addClass('table-bordered table-hover');
selector.find('tbody').on('click', 'tr', function () {
var table = selector.DataTable();
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
}
else {
table.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
});
});
Template.databaseDumpRestore.events({
......@@ -16,24 +33,41 @@ Template.databaseDumpRestore.events({
var laddaButton = $('#btnTakeDump').ladda();
laddaButton.ladda('start');
toastr.success('Started dump process you can NOT do anything till the process finishes.');
Meteor.call('takeDump', connection, settings.dumpPath, function (err, result) {
console.log(err);
console.log(result);
Meteor.call('takeDump', connection, settings.dumpPath, function (err) {
if (err) {
toastr.error("Couldn't take dump, " + err.message);
}
else {
toastr.success('A background process to take a dump has started, whenever it finishes you can see the dump on this page');
}
Ladda.stopAll();
});
},
'click .editor_download': function (e) {
'click .editor_import': function (e) {
e.preventDefault();
var connection = Connections.findOne({_id: Session.get(Template.strSessionConnection)});
var table = $('#tblDumps').DataTable();
},
if (table.row(this).data()) {
var laddaButton = $('#btnTakeDump').ladda();
laddaButton.ladda('start');
'click .editor_import': function (e) {
e.preventDefault();
$('#connectionEditModal').modal('show');
var dumpInfo = table.row(this).data();
dumpInfo.status = DUMP_STATUS.IN_PROGRESS;
Meteor.call('updateDump', dumpInfo); // this is a simple update to notify user on UI
Meteor.call('restoreDump', connection, dumpInfo, function (err) {
if (err) {
toastr.error("Couldn't restore dump, " + err.message);
}
else {
toastr.success('A background process to restore the dump(' + dumpInfo.filePath + ') has started, whenever it finishes you can see the result on this page');
}
Ladda.stopAll();
});
}
}
});
......@@ -52,24 +86,69 @@ Template.databaseDumpRestore.helpers({
className: 'center',
sClass: "hide_column"
},
{
title: 'Connection name',
data: 'connectionName',
width: '20%',
className: 'center'
},
{
title: 'Date',
data: 'date',
width: '15%',
render: function (cellData) {
return moment(cellData).format('YYYY-MM-DD HH:mm:ss');
},
className: 'center'
},
{
title: 'File Path',
data: 'filePath',
width: '30%',
className: 'center'
},
{
title: 'Size',
data: 'sizeInBytes',
width: '10%',
render: function (cellData) {
var scale = 1;
var text = "Bytes";
var settings = Settings.findOne();
switch (settings.scale) {
case "MegaBytes":
scale = 1024 * 1024;
text = "MBs";
break;
case "KiloBytes":
scale = 1024;
text = "KBs";
break;
default:
scale = 1;
text = "Bytes";
break;
}
var result = isNaN(Number(cellData / scale).toFixed(2)) ? "0.00" : Number(cellData / scale).toFixed(2);
return result + " " + text;
},
className: 'center'
},
{
title: 'Import',
data: null,
className: 'center',
bSortable: false,
defaultContent: '<a href="" title="Import" class="editor_import"><i class="fa fa-database text-navy"></i></a>'
title: 'Import Status',
data: 'status',
width: '15%',
className: 'center'
},
{
title: 'Download',
title: 'Import',
data: null,
className: 'center',
width: '10%',
bSortable: false,
defaultContent: '<a href="" title="Download" class="editor_download"><i class="fa fa-download text-navy"></i></a>'
defaultContent: '<a href="" title="Import" class="editor_import"><i class="fa fa-database text-navy"></i></a>'
}
]
}
......
......@@ -13,5 +13,15 @@ Dumps.attachSchema(new SimpleSchema({
sizeInBytes: {
type: "Number"
},
filePath: {
type: "String"
},
status: {
type: "String",
defaultValue: "Not Imported",
allowedValues: ['In Progress', 'Not Imported', "Finished", "Error"]
}
}));
......@@ -115,4 +115,11 @@ FINDONE_MODIFY_OPTIONS = {
SORT: "sort",
UPSERT: "upsert",
RETURN_ORIGINAL: "returnOriginal"
};
DUMP_STATUS = {
IN_PROGRESS: "In Progress",
NOT_IMPORTED: "Not Imported",
FINISHED: "Finished",
ERROR: "Error"
};
\ No newline at end of file
{
"mongodb": "2.1.2",
"mongodb-backup": "1.5.1"
"mongodb-backup": "1.5.1",
"mongodb-restore": "1.5.1"
}
\ No newline at end of file
......@@ -209,6 +209,172 @@
}
}
}
},
"mongodb-restore": {
"version": "1.5.1",
"dependencies": {
"bson": {
"version": "0.4.20"
},
"mongodb": {
"version": "2.1.3",
"dependencies": {
"mongodb-core": {
"version": "1.2.31"
},
"readable-stream": {
"version": "1.0.31",
"dependencies": {
"core-util-is": {
"version": "1.0.2"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.31"
},
"inherits": {
"version": "2.0.1"
}
}
},
"es6-promise": {
"version": "3.0.2"
},
"kerberos": {
"version": "0.0.18",
"dependencies": {
"nan": {
"version": "2.0.9"
}
}
}
}
},
"logger-request": {
"version": "3.6.0",
"dependencies": {
"basic-authentication": {
"version": "1.6.2",
"dependencies": {
"setheaders": {
"version": "0.1.5"
}
}
},
"on-finished": {
"version": "2.3.0",
"dependencies": {
"ee-first": {
"version": "1.1.1"
}
}
},
"transfer-rate": {
"version": "1.1.3"
},
"winston": {
"version": "2.1.1",
"dependencies": {
"async": {
"version": "1.0.0"
},
"colors": {
"version": "1.0.3"
},
"cycle": {
"version": "1.0.3"
},
"eyes": {
"version": "0.1.8"
},
"isstream": {
"version": "0.1.2"
},
"pkginfo": {
"version": "0.3.1"
},
"stack-trace": {
"version": "0.0.9"
}
}
}
}
},
"tar": {
"version": "2.2.1",
"dependencies": {
"block-stream": {
"version": "0.0.8"
},
"fstream": {
"version": "1.0.8",
"dependencies": {
"graceful-fs": {
"version": "4.1.2"
},
"mkdirp": {
"version": "0.5.1",
"dependencies": {
"minimist": {
"version": "0.0.8"
}
}
},
"rimraf": {
"version": "2.5.1",
"dependencies": {
"glob": {
"version": "6.0.4",
"dependencies": {
"inflight": {
"version": "1.0.4",
"dependencies": {
"wrappy": {
"version": "1.0.1"
}
}
},
"minimatch": {
"version": "3.0.0",
"dependencies": {
"brace-expansion": {
"version": "1.1.2",
"dependencies": {
"balanced-match": {
"version": "0.3.0"
},
"concat-map": {
"version": "0.0.1"
}
}
}
}
},
"once": {
"version": "1.3.3",
"dependencies": {
"wrappy": {
"version": "1.0.1"
}
}
},
"path-is-absolute": {
"version": "1.0.0"
}
}
}
}
}
}
},
"inherits": {
"version": "2.0.1"
}
}
}
}
}
}
}
base64@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
deps@1.0.7
ejson@1.0.6
ephemer:reactive-datatables@1.1.0
geojson-utils@1.0.3
html-tools@1.0.4
htmljs@1.0.4
id-map@1.0.3
jquery@1.11.3_2
json@1.0.3
meteor@1.1.6
minifiers@1.1.5
minimongo@1.0.8
observe-sequence@1.0.6
ordered-dict@1.0.3
random@1.0.3
reactive-var@1.0.5
spacebars-compiler@1.0.6
templating@1.1.1
tracker@1.0.7
underscore@1.0.3
The MIT License (MIT)
Copyright (c) 2014 Geordie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Reactive Datatables
Provides a [meteor.js](http://www.meteor.com) way of using [jquery.dataTables](http://datatables.net/) with reactively-updating data, instant search, state saving / pagination etc.
## Installation
`meteor add ephemer:reactive-datatables`
## Usage
In your template:
<template name="containsTheDataTable">
{{> ReactiveDatatable tableData=reactiveDataFunction options=optionsObject }}
</template>
**Important:** Due to the way Blaze interprets parameters upon calling a template, `reactiveDataFunction` should *return a __function__ that returns an array*, not return the data itself. I'm sure there's a cleverer way to do this, but it works for now:
dataTableData = function () {
return Meteor.users.find().fetch(); // or .map()
};
Template.containsTheDataTable.helpers({
reactiveDataFunction: function () {
return dataTableData;
},
optionsObject: optionsObject // see below
});
Set up your datatable's options as per the jquery.dataTables API, e.g.:
var optionsObject = {
columns: [{
title: 'Real Name',
data: 'profile.realname', // note: access nested data like this
className: 'nameColumn'
}, {
title: 'Photo',
data: 'profile.picture',
render: renderPhoto, // optional data transform, see below
className: 'imageColumn'
}],
// ... see jquery.dataTables docs for more
}
function renderPhoto(cellData, renderType, currentRow) {
// You can return html strings, change sort order etc. here
// Again, see jquery.dataTables docs
var img = "<img src='" + cellData + "' title='" + currentRow.profile.realname + "'>"
return img;
}
I've deliberately kept this package as close as possible to the original API. I've also deliberately not exposed any global variables, although you can access the DataTable API in the usual jquery way using the '#datatable' selector from your template, i.e., to get an array with your data:
`$('#datatable').DataTable().rows()`
## About returning a function
The reason you need to return a function with an array and not just the array itself (see code example above) is because the autorun / reactive context is on the datatable end (which is how the datatable knows to do the reactive updates).
If you just pass an array into the datatable (this would involve a 1-line source code change), it won't know when the array has been updated. This is because arrays by themselves don't have an .invalidate() function attached. The trick of passing a wrapped function into the datatable's template is the only way I can see to encapsulate the datatable functionality in a package. Please send me a pull request if you can see a better way around this.
## Acknowledgements
Thank you to @smowden for finding the key to getting this whole package off the ground: `$('#datatable').DataTable().clear().rows.add(data).draw()`
此差异已折叠。
Package.describe({
name: 'ephemer:reactive-datatables-modified',
summary: "Fast and reactive jQuery DataTables using standard Cursors / DataTables API. Supports Bootstrap 3.",
version: "1.1.0",
git: "https://github.com/ephemer/meteor-reactive-datatables.git"
});
Package.onUse(function(api) {
api.versionsFrom('0.9.0');
api.use(['templating'], 'client');
api.addFiles([
'jquery.dataTables.min.js',
'reactive-datatables.js',
'reactive-datatable-template.html',
'reactive-datatable-template.js'
], 'client');
});
<template name="ReactiveDatatable">
<div class="datatable_wrapper"></div>
</template>
\ No newline at end of file
Template.ReactiveDatatable.rendered = function () {
var data = this.data;
if (typeof data.tableData !== "function") {
throw new Meteor.Error('Your tableData must be a function that returns an array via Cursor.fetch(), .map() or another (hopefully reactive) means')
}
var reactiveDataTable = new ReactiveDatatable(data.options);
// Help Blaze cleanly remove entire datatable when changing template / route by
// wrapping table in existing element (#datatable_wrap) defined in the template.
var table = document.createElement('table');
table.className = 'table dataTable';
table.id = data.tableId;
// Render the table element and turn it into a DataTable
this.$('.datatable_wrapper').append(table);
var dt = $(table).DataTable(data.options);
reactiveDataTable.datatable = dt;
dt.on('page.dt', function () {
var info = dt.page.info();
reactiveDataTable.page = info.page;
});
this.autorun(function () {
reactiveDataTable.update(data.tableData());
});
};
\ No newline at end of file
/*Tinytest.add('example', function (test) {
test.equal(true, true);
});
*/
\ No newline at end of file
ReactiveDatatable = function(options) {
var self = this;
this.options = options = _.defaults(options, {
// Any of these can be overriden by passing an options
// object into your ReactiveDatatable template (see readme)
stateSave: true,
stateDuration: -1, // Store data for session only
pageLength: 5,
lengthMenu: [3, 5, 10, 50, 100],
columnDefs: [{ // Global default blank value to avoid popup on missing data
targets: '_all',
defaultContent: '–––'
}],
stateLoadParams: function(settings, data) {
// Make it easy to change to the stored page on .update()
self.page = data.start / data.length;
}
});
};
ReactiveDatatable.prototype.update = function(data) {
if (!data) return;
var self = this;
self.datatable
.clear()
.rows.add(data)
.draw(false)
.page(self.page || 0) // XXX: Can we avoid drawing twice?
.draw(false); // I couldn't get the page drawing to work otherwise
};
{
"dependencies": [
[
"blaze",
"1.0.3"
],
[
"deps",
"1.0.1"
],
[
"ejson",
"1.0.0"
],
[
"geojson-utils",
"1.0.0"
],
[
"htmljs",
"1.0.0"
],
[
"id-map",
"1.0.0"
],
[
"jquery",
"1.0.0"
],
[
"json",
"1.0.0"
],
[
"meteor",
"1.0.2"
],
[
"minimongo",
"1.0.1"
],
[
"observe-sequence",
"1.0.1"
],
[
"ordered-dict",
"1.0.0"
],
[
"random",
"1.0.0"
],
[
"templating",
"1.0.4"
],
[
"ui",
"1.0.0"
],
[
"underscore",
"1.0.0"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.26",
"format": "1.0"
}
\ No newline at end of file
......@@ -2,6 +2,22 @@
* Created by RSercan on 26.12.2015.
*/
Meteor.methods({
'updateDump': function (dump) {
Dumps.update({_id: dump._id}, {
$set: {
connectionName: dump.connectionName,
date: dump.date,
sizeInBytes: dump.sizeInBytes,
filePath: dump.filePath,
status: dump.status
}
});
},
'saveDump': function (dump) {
Dumps.insert(dump);
},
'updateSettings': function (settings) {
Settings.update({}, {
$set: {
......
......@@ -2,28 +2,67 @@
* Created by RSercan on 17.1.2016.
*/
Meteor.methods({
'restoreDump': function (connection, dumpInfo) {
var connectionUrl = getConnectionUrl(connection);
var restore = Meteor.npmRequire('mongodb-restore');
var path = dumpInfo.filePath.substring(0, dumpInfo.filePath.indexOf('/'));
var fileName = dumpInfo.filePath.substring(dumpInfo.filePath.indexOf('/') + 1);
console.log('[DUMP] Restoring dump ' + JSON.stringify(dumpInfo) + ' to the ' + connectionUrl);
try {
restore({
uri: connectionUrl,
root: path,
tar: fileName,
drop: true,
callback: Meteor.bindEnvironment(function () {
dumpInfo.status = DUMP_STATUS.FINISHED;
console.log("[DUMP] Dump has successfuly restored: " + JSON.stringify(dumpInfo));
Meteor.call('updateDump', dumpInfo);
})
});
}
catch (ex) {
console.log('[DUMP] Unexpected exception during dump process: ', ex);
dumpInfo.status = DUMP_STATUS.ERROR;
Meteor.call('updateDump', dumpInfo);
}
},
'takeDump': function (connection, path) {
var date = new Date();
var connectionUrl = getConnectionUrl(connection);
var fileName = connection.databaseName + "_" + new Date().getTime() + ".tar";
var fileName = connection.databaseName + "_" + date.getTime() + ".tar";
var fullFilePath = path + "/" + fileName;
var backup = Meteor.npmRequire('mongodb-backup');
console.log('[DUMP] Taking dump to the path: ' + path + " with fileName: " + fileName);
return Async.runSync(function (done) {
try {
backup({
uri: connectionUrl,
root: path,
tar: fileName,
//stream :
callback: function () {
console.log('[DUMP] Successfuly took dump to ' + path + "/" + fileName);
done(null, {"result": "ok"});
}
});
}
catch (ex) {
done(new Meteor.Error(ex.message), null);
}
});
try {
backup({
uri: connectionUrl,
root: path,
tar: fileName,
//stream :
callback: Meteor.bindEnvironment(function () {
var fs = Meteor.npmRequire('fs');
var stats = fs.statSync(fullFilePath);
var dump = {
filePath: fullFilePath,
date: date,
connectionName: connection.name,
sizeInBytes: stats["size"],
status: DUMP_STATUS.NOT_IMPORTED
};
console.log("[DUMP] Trying to save dump: " + JSON.stringify(dump));
Meteor.call('saveDump', dump);
console.log('[DUMP] Dump process has finished');
})
});
}
catch (ex) {
console.log('Unexpected exception during dump process: ', ex);
}
}
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册