Fixing outstanding installer & port mapping bugs

This commit is contained in:
Jeff Morgan 2014-11-28 17:56:38 -05:00
parent d1c6928321
commit 0357ce7b0e
23 changed files with 467 additions and 340 deletions

View File

@ -124,7 +124,10 @@ app.on('ready', function() {
width: 800, width: 800,
height: 578, height: 578,
resizable: false, resizable: false,
frame: false frame: false,
'web-preferences': {
'web-security': false
}
}; };
mainWindow = new BrowserWindow(windowOptions); mainWindow = new BrowserWindow(windowOptions);
mainWindow.hide(); mainWindow.hide();

View File

@ -1,6 +1,7 @@
var exec = require('exec'); var exec = require('exec');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var async = require('async');
var Convert = require('ansi-to-html'); var Convert = require('ansi-to-html');
var convert = new Convert(); var convert = new Convert();
@ -8,9 +9,10 @@ AppUtil = {};
AppUtil.run = function (app, callback) { AppUtil.run = function (app, callback) {
var image = Images.findOne({_id: app.imageId}); var image = Images.findOne({_id: app.imageId});
// Delete old container if one already exists Apps.update(app._id, {$set: {
status: 'STARTING'
}});
Docker.removeContainer(app.name, function (err) { Docker.removeContainer(app.name, function (err) {
if (err) { callback(err); }
Docker.runContainer(app, image, function (err, container) { Docker.runContainer(app, image, function (err, container) {
if (err) { callback(err); } if (err) { callback(err); }
Docker.getContainerData(container.id, function (err, data) { Docker.getContainerData(container.id, function (err, data) {
@ -26,22 +28,6 @@ AppUtil.run = function (app, callback) {
}); });
}; };
AppUtil.restartHelper = function (app) {
if (app.docker && app.docker.Id) {
var container = Docker.client().getContainer(app.docker.Id);
container.restart(function (err) {
if (err) { console.error(err); }
Docker.getContainerData(app.docker.Id, function (err, data) {
if (err) { console.error(err); }
Apps.update(app._id, {$set: {
status: 'READY',
docker: data
}});
});
});
}
};
AppUtil.start = function (appId) { AppUtil.start = function (appId) {
var app = Apps.findOne(appId); var app = Apps.findOne(appId);
if (app && app.docker) { if (app && app.docker) {
@ -78,16 +64,6 @@ AppUtil.stop = function (appId) {
} }
}; };
AppUtil.restart = function (appId) {
var app = Apps.findOne(appId);
if (app && app.docker) {
Apps.update(app._id, {$set: {
status: 'STARTING'
}});
AppUtil.restartHelper(app);
}
};
AppUtil.remove = function (appId) { AppUtil.remove = function (appId) {
var app = Apps.findOne(appId); var app = Apps.findOne(appId);
Apps.remove({_id: appId}); Apps.remove({_id: appId});
@ -153,35 +129,14 @@ AppUtil.recover = function () {
}); });
}; };
AppUtil.sync = function () { AppUtil.sync = function (callback) {
Docker.listContainers(function (err, containers) { Docker.listContainers(function (err, containers) {
if (err) { if (err) {
console.error(err); callback(err);
} else { return;
}
var apps = Apps.find({}).fetch(); var apps = Apps.find({}).fetch();
_.each(apps, function (app) {
var app = Apps.findOne(app._id);
if (app && app.docker && app.docker.Id) {
var duplicateApps = Apps.find({'docker.Id': app.docker.Id, _id: {$ne: app._id}}).fetch();
_.each(duplicateApps, function (duplicateApp) {
Apps.remove(duplicateApp._id);
});
Docker.getContainerData(app.docker.Id, function (err, data) {
var status = 'STARTING';
if (data && data.State && data.State.Running) {
status = 'READY';
} else if (data && data.State && !data.State.Running) {
status = 'ERROR';
}
Apps.update(app._id, {
$set: {
docker: data,
status: status
}
})
});
}
});
var dockerIds = _.map(apps, function (app) { var dockerIds = _.map(apps, function (app) {
if (app.docker && app.docker.Id) { if (app.docker && app.docker.Id) {
return app.docker.Id; return app.docker.Id;
@ -235,12 +190,42 @@ AppUtil.sync = function () {
path: appPath, path: appPath,
logs: [], logs: [],
createdAt: new Date(), createdAt: new Date(),
volumesEnabled: true
}; };
if (container.HostConfig.Binds && container.HostConfig.Binds.length) {
appObj.volumesEnabled = true;
} else {
appObj.volumesEnabled = false;
}
console.log(appObj); console.log(appObj);
Apps.insert(appObj); Apps.insert(appObj);
} }
}); });
async.each(apps, function (app, callback) {
if (app && app.docker && app.docker.Id) {
var duplicateApps = Apps.find({'docker.Id': app.docker.Id, _id: {$ne: app._id}}).fetch();
_.each(duplicateApps, function (duplicateApp) {
Apps.remove(duplicateApp._id);
});
Docker.getContainerData(app.docker.Id, function (err, data) {
if (err) {callback(err); return;}
var status = 'STARTING';
if (data && data.State && data.State.Running) {
status = 'READY';
} else if (data && data.State && !data.State.Running) {
status = 'ERROR';
}
Apps.update(app._id, {
$set: {
docker: data,
status: status
} }
}); });
callback();
});
}
}, function (err) {
callback(err);
});
});
}; };

View File

@ -2,11 +2,12 @@ var exec = require('exec');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var async = require('async');
var pkginfo = require('pkginfo')(module);
Boot2Docker = {}; Boot2Docker = {};
Boot2Docker.VERSION = module.exports['boot2docker-version'];
Boot2Docker.REQUIRED_IP = '192.168.60.103';
Boot2Docker.VERSION = '1.3.1';
Boot2Docker.command = function () { Boot2Docker.command = function () {
return path.join(Util.getBinDir(), 'boot2docker-' + Boot2Docker.VERSION); return path.join(Util.getBinDir(), 'boot2docker-' + Boot2Docker.VERSION);
@ -252,7 +253,43 @@ Boot2Docker.vmUpToDate = function (callback) {
if (err) { if (err) {
callback(err); return; callback(err); return;
} }
var index = data.indexOf('Boot2Docker-v' + Boot2Docker.VERSION); var match = data.match(/Boot2Docker-v(\d+\.\d+\.\d+)/);
callback(null, index !== -1); if (!match) {
}); callback('Could not parse boot2docker iso version');
return;
} }
callback (null, Util.compareVersions(match[1], Boot2Docker.VERSION) < 0);
});
};
Boot2Docker.status = function (callback) {
this.exec('status', function (stderr, stdout, code) {
if (code) {callback(stderr); return;}
callback(null, stdout.trim());
});
};
Boot2Docker.portAvailable = function (port, protocol, callback) {
this.exec('ssh netstat -lntu | grep LISTEN | grep ' + protocol + ' | grep -c ":::' + port + '\\s"', function (stdout, stderr, code) {
if (stderr.trim() === '0') {
callback(true);
} else {
callback(false);
}
});
};
Boot2Docker.waitWhileStatus = function (status, callback) {
var current = status;
async.whilst(function () {
return current === status;
}, function (innerCallback) {
Boot2Docker.status(function (err, vmStatus) {
if (err) {innerCallback(err); return;}
current = vmStatus.trim();
innerCallback();
});
}, function (err) {
callback(err);
});
};

View File

@ -95,13 +95,23 @@ Docker.runContainer = function (app, image, callback) {
var builtStr = key + '=' + app.config[key]; var builtStr = key + '=' + app.config[key];
envParam.push(builtStr); envParam.push(builtStr);
}); });
Docker.client().createContainer({
var containerOpts = {
Image: image.docker.Id, Image: image.docker.Id,
Tty: false, Tty: false,
Env: envParam, Env: envParam,
Hostname: app.name, Hostname: app.name,
name: app.name name: app.name
}, function (err, container) { };
if (app.docker && app.docker.NetworkSettings.Ports) {
containerOpts.ExposedPorts = app.docker.NetworkSettings.Ports;
}
console.log(containerOpts);
Docker.client().createContainer(containerOpts, function (err, container) {
if (err) { callback(err, null); return; } if (err) { callback(err, null); return; }
console.log('Created container: ' + container.id); console.log('Created container: ' + container.id);
// Bind volumes // Bind volumes
@ -111,11 +121,19 @@ Docker.runContainer = function (app, image, callback) {
binds.push([Util.getHomePath(), 'Kitematic', app.name, vol.Path].join('/') + ':' + vol.Path); binds.push([Util.getHomePath(), 'Kitematic', app.name, vol.Path].join('/') + ':' + vol.Path);
}); });
} }
// Start the container
container.start({ var startOpts = {
PublishAllPorts: true,
Binds: binds Binds: binds
}, function (err) { };
if (app.docker && app.docker.NetworkSettings.Ports) {
startOpts.PortBindings = app.docker.NetworkSettings.Ports;
} else {
startOpts.PublishAllPorts = true;
}
console.log(startOpts);
container.start(startOpts, function (err) {
if (err) { callback(err, null); return; } if (err) { callback(err, null); return; }
console.log('Started container: ' + container.id); console.log('Started container: ' + container.id);
callback(null, container); callback(null, container);

View File

@ -2,11 +2,11 @@ FormSchema = {
formCreateApp: { formCreateApp: {
name: { name: {
label: 'app name', label: 'container name',
required: true, required: true,
transforms: ['clean', 'slugify'], transforms: ['clean', 'slugify'],
messages: { messages: {
'uniqueAppName': "This app name is already being used." 'uniqueAppName': "This container name is already being used."
}, },
rules: { rules: {
uniqueAppName: true uniqueAppName: true

View File

@ -3,6 +3,7 @@ var convert = new Convert();
var exec = require('exec'); var exec = require('exec');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var async = require('async');
ImageUtil = {}; ImageUtil = {};
@ -239,55 +240,34 @@ ImageUtil.remove = function (imageId) {
} }
}; };
ImageUtil.sync = function () { ImageUtil.sync = function (callback) {
Docker.listImages(function (err, dockerImages) { Docker.listImages(function (err, dockerImages) {
if (err) { if (err) {
console.error(err); console.error(err);
} else { return;
}
var images = Images.find({}).fetch(); var images = Images.find({}).fetch();
_.each(images, function (image) {
var image = Images.findOne(image._id); // Delete missing GUI images
if (image && image.docker && image.docker.Id) { var kitematicIds = _.map(images, function (image) {
var duplicateImages = Images.find({'docker.Id': image.docker.Id, _id: {$ne: image._id}}).fetch();
_.each(duplicateImages, function (duplicateImage) {
Images.remove(duplicateImage._id);
});
var imageData = _.find(dockerImages, function (dockerImage) {
return dockerImage.Id === image.docker.Id;
});
if (imageData && imageData.RepoTags) {
Images.update(image._id, {
$set: {
tags: imageData.RepoTags
}
});
}
Docker.getImageData(image.docker.Id, function (err, data) {
Images.update(image._id, {
$set: {
docker: data
}
})
});
}
});
var dockerIds = _.map(images, function (image) {
if (image.docker && image.docker.Id) { if (image.docker && image.docker.Id) {
return image.docker.Id; return image.docker.Id;
} }
}); });
var imageIds = _.map(dockerImages, function (image) { var daemonIds = _.map(daemonIds, function (image) {
return image.Id; return image.Id;
}); });
var diffImages = _.difference(dockerIds, imageIds); var diffImages = _.difference(kitematicIds, daemonIds);
_.each(diffImages, function (imageId) { _.each(diffImages, function (imageId) {
var image = Images.findOne({'docker.Id': imageId}); var image = Images.findOne({'docker.Id': imageId});
if (image && image.status !== 'BUILDING') { if (image && image.status !== 'BUILDING') {
ImageUtil.remove(image._id); Images.remove(image._id);
} }
}); });
// Add missing Daemon images
var diffDockerImages = _.reject(dockerImages, function (image) { var diffDockerImages = _.reject(dockerImages, function (image) {
return _.contains(dockerIds, image.Id); return _.contains(kitematicIds, image.Id);
}); });
_.each(diffDockerImages, function (image) { _.each(diffDockerImages, function (image) {
var repoTag = _.first(image.RepoTags); var repoTag = _.first(image.RepoTags);
@ -312,6 +292,36 @@ ImageUtil.sync = function () {
Images.insert(imageObj); Images.insert(imageObj);
} }
}); });
async.each(images, function (image, callback) {
var image = Images.findOne(image._id);
if (image && image.docker && image.docker.Id) {
var duplicateImages = Images.find({'docker.Id': image.docker.Id, _id: {$ne: image._id}}).fetch();
_.each(duplicateImages, function (duplicateImage) {
Images.remove(duplicateImage._id);
});
var imageData = _.find(dockerImages, function (dockerImage) {
return dockerImage.Id === image.docker.Id;
});
if (imageData && imageData.RepoTags) {
Images.update(image._id, {
$set: {
tags: imageData.RepoTags
} }
}); });
}
Docker.getImageData(image.docker.Id, function (err, data) {
if (err) {callback(err); return;}
Images.update(image._id, {
$set: {
docker: data
}
});
callback();
});
}
}, function (err) {
callback(err);
});
});
}; };

View File

@ -52,7 +52,7 @@ Router.map(function () {
} }
Session.set('onIntro', false); Session.set('onIntro', false);
startUpdatingBoot2DockerUtilization(); startUpdatingBoot2DockerUtilization();
// startSyncingAppState(); startSyncingAppState();
Router.go('dashboard_apps'); Router.go('dashboard_apps');
} }
}); });

View File

@ -67,6 +67,17 @@ Setup.steps = [
}, },
message: 'Downloading VirtualBox...' message: 'Downloading VirtualBox...'
}, },
{
run: function (callback) {
VirtualBox.shutdownVM('kitematic-vm', function (err, removed) {
if (err) {
console.log(err);
}
callback();
});
},
message: 'Cleaning up existing Docker VM...'
},
// Initialize Boot2Docker if necessary. // Initialize Boot2Docker if necessary.
{ {
@ -74,10 +85,6 @@ Setup.steps = [
Boot2Docker.exists(function (err, exists) { Boot2Docker.exists(function (err, exists) {
if (err) { callback(err); return; } if (err) { callback(err); return; }
if (!exists) { if (!exists) {
var vmFilesPath = path.join(Util.getHomePath(), 'VirtualBox\ VMs', 'boot2docker-vm');
if (fs.existsSync(vmFilesPath)) {
Util.deleteFolder(vmFilesPath);
}
Boot2Docker.init(function (err) { Boot2Docker.init(function (err) {
callback(err); callback(err);
}); });
@ -105,9 +112,10 @@ Setup.steps = [
}, },
{ {
run: function (callback) { run: function (callback) {
Boot2Docker.state(function (err, state) { Boot2Docker.waitWhileStatus('saving', function (err) {
Boot2Docker.status(function (err, status) {
if (err) {callback(err); return;} if (err) {callback(err); return;}
if (state !== 'running') { if (status !== 'running') {
Boot2Docker.start(function (err) { Boot2Docker.start(function (err) {
callback(err); callback(err);
}); });
@ -115,6 +123,7 @@ Setup.steps = [
callback(); callback();
} }
}); });
});
}, },
message: 'Starting the Docker VM...' message: 'Starting the Docker VM...'
}, },

View File

@ -1,6 +1,7 @@
var fs = require('fs'); var fs = require('fs');
var exec = require('exec'); var exec = require('exec');
var path = require('path'); var path = require('path');
var async = require('async');
VirtualBox = {}; VirtualBox = {};
@ -51,7 +52,7 @@ VirtualBox.version = function (callback) {
}); });
}; };
VirtualBox.shutDownRunningVMs = function (callback) { VirtualBox.saveRunningVMs = function (callback) {
if (!this.installed()) { if (!this.installed()) {
callback('VirtualBox not installed.'); callback('VirtualBox not installed.');
return; return;
@ -66,7 +67,7 @@ VirtualBox.shutDownRunningVMs = function (callback) {
}; };
VirtualBox.killAllProcesses = function (callback) { VirtualBox.killAllProcesses = function (callback) {
this.shutDownRunningVMs(function (err) { this.saveRunningVMs(function (err) {
if (err) {callback(err); return;} if (err) {callback(err); return;}
exec('pkill VirtualBox', function (stderr, stdout, code) { exec('pkill VirtualBox', function (stderr, stdout, code) {
if (code) {callback(stderr); return;} if (code) {callback(stderr); return;}
@ -77,3 +78,41 @@ VirtualBox.killAllProcesses = function (callback) {
}); });
}); });
}; };
VirtualBox.vmState = function (name, callback) {
VirtualBox.exec('showvminfo ' + name + ' --machinereadable', function (stderr, stdout, code) {
if (code) { callback(stderr); return; }
var match = stdout.match(/VMState="(\w+)"/);
if (!match) {
callback('Could not parse VMState');
return;
}
callback(null, match[1]);
});
};
VirtualBox.shutdownVM = function (name, callback) {
VirtualBox.vmState(name, function (err, state) {
// No VM found
if (err) { callback(null, false); return; }
VirtualBox.exec('controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) {
if (code) { callback(stderr, false); return; }
var state = null;
async.until(function () {
return state === 'poweroff';
}, function (callback) {
VirtualBox.vmState(name, function (err, newState) {
if (err) { callback(err); return; }
state = newState;
setTimeout(callback, 250);
});
}, function (err) {
VirtualBox.exec('unregistervm ' + name + ' --delete', function (stderr, stdout, code) {
if (code) { callback(err); return; }
callback();
});
});
});
});
};

View File

@ -100,10 +100,11 @@ startUpdatingBoot2DockerUtilization = function () {
}; };
startSyncingAppState = function () { startSyncingAppState = function () {
ImageUtil.sync(); ImageUtil.sync(function (err) {
AppUtil.sync(); if (err) {console.log(err);}
Meteor.setTimeout(function () { AppUtil.sync(function (err) {
ImageUtil.sync(); if (err) {console.log(err);}
AppUtil.sync(); Meteor.setTimeout(startSyncingAppState, 5000);
}, 5000); });
});
}; };

View File

@ -30,8 +30,11 @@
} }
} }
.ports { .ports {
position: relative;
top: -2px;
.dropdown-menu { .dropdown-menu {
min-width: 241px; min-width: 241px;
padding: 10px 15px 3px;
} }
.btn-group { .btn-group {
top: -2px; top: -2px;
@ -62,7 +65,6 @@
&.open + .tooltip { &.open + .tooltip {
display: none !important; display: none !important;
} }
position: relative;
} }
} }
&:hover { &:hover {
@ -281,10 +283,18 @@
} }
} }
.form-env-vars {
label {
font-size: 13px;
}
input {
font-size: 13px;
padding: 5px 9px;
}
.env-var-pair { .env-var-pair {
.make-row(); .make-row();
margin-bottom: 0.2em; margin-bottom: 0.2em;
font-size: 12px; font-size: 13px;
.env-var-key { .env-var-key {
.make-xs-column(5); .make-xs-column(5);
text-overflow: ellipsis; text-overflow: ellipsis;
@ -301,10 +311,10 @@
.make-xs-column(2); .make-xs-column(2);
} }
} }
}
.app-ports { .app-ports {
cursor: default; cursor: default;
padding: 10px 15px 3px;
min-width: 240px; min-width: 240px;
li { li {
padding-bottom: 7px; padding-bottom: 7px;

View File

@ -1,15 +1,6 @@
<template name="dashboardAppsPorts"> <template name="dashboardAppsPorts">
<div class="app-ports"> <div class="app-ports">
{{#each ports}} {{#each ports}}
{{#if this.web}}
<li role="menuitem">
<span class="port-wrapper">Port {{this.port}}</span>
<span class="arrow-wrapper"></span>
<span class="open-button-wrapper">
<a href="{{this.url}}" onclick="trackLink('view container')" class="btn btn-action btn-xs btn-view-port">Open in Browser</a>
</span>
</li>
{{else}}
<li role="menuitem"> <li role="menuitem">
<span class="port-wrapper">Port {{this.port}}</span> <span class="port-wrapper">Port {{this.port}}</span>
<span class="arrow-wrapper"> <span class="arrow-wrapper">
@ -17,7 +8,6 @@
</span> </span>
<span class="host-address-wrapper">{{this.hostIp}}:{{this.hostPort}}</span> <span class="host-address-wrapper">{{this.hostIp}}:{{this.hostPort}}</span>
</li> </li>
{{/if}}
{{/each}} {{/each}}
</div> </div>
</template> </template>

View File

@ -1,4 +1,14 @@
<template name="dashboardAppsSettings"> <template name="dashboardAppsSettings">
<div class="section">
<div class="left-section">
<h4>Ports</h4>
</div>
<div class="right-section">
<ul class="list-unstyled">
{{> dashboardAppsPorts}}
</ul>
</div>
</div>
<div class="section"> <div class="section">
<div class="left-section"> <div class="left-section">
<h4>Environment Variables</h4> <h4>Environment Variables</h4>

View File

@ -45,7 +45,7 @@ Template.dashboardAppsSettings.events({
e.preventDefault(); e.preventDefault();
var appId = this._id; var appId = this._id;
dialog.showMessageBox({ dialog.showMessageBox({
message: 'Are you sure you want to delete this app?', message: 'Are you sure you want to delete this container?',
buttons: ['Delete', 'Cancel'] buttons: ['Delete', 'Cancel']
}, function (index) { }, function (index) {
if (index === 0) { if (index === 0) {

View File

@ -23,6 +23,7 @@
</h5> </h5>
<div class="options"> <div class="options">
{{#if $eq status 'READY'}} {{#if $eq status 'READY'}}
{{#if ports}}
{{#if viewPort}} {{#if viewPort}}
<div class="ports btn-group btn-icon" title="View" data-placement="bottom"> <div class="ports btn-group btn-icon" title="View" data-placement="bottom">
<a href="{{viewPort.url}}" onclick="trackLink('view container')" class="btn btn-action btn-xs btn-view btn-globe"> <a href="{{viewPort.url}}" onclick="trackLink('view container')" class="btn btn-action btn-xs btn-view btn-globe">
@ -50,10 +51,11 @@
</span> </span>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/if}}
<a href="#" onclick="trackLink('open container folder')" class="btn-icon btn-folder" data-toggle="tooltip" data-placement="bottom" title="Volumes"><span class="typcn typcn-folder"></span></a> <a href="#" onclick="trackLink('open container folder')" class="btn-icon btn-folder" data-toggle="tooltip" data-placement="bottom" title="Volumes"><span class="typcn typcn-folder"></span></a>
{{#if $eq status 'READY'}} {{#if $eq status 'READY'}}
<a href="#" onclick="trackLink('terminal into container')" class="btn-icon btn-terminal" data-toggle="tooltip" data-placement="bottom" title="Terminal"><span class="typcn typcn-device-laptop"></span></a> <a href="#" onclick="trackLink('terminal into container')" class="btn-icon btn-terminal" data-toggle="tooltip" data-placement="bottom" title="Terminal"><span class="typcn typcn-device-laptop"></span></a>
<a href="#" onclick="trackLink('stop container')" class="btn-icon btn-stop" data-toggle="tooltip" data-placement="bottom" title="Stop"><span class="typcn typcn-media-pause-outline"></span></a> <a href="#" onclick="trackLink('stop container')" class="btn-icon btn-stop" data-toggle="tooltip" data-placement="bottom" title="Stop"><span class="typcn typcn-media-stop-outline"></span></a>
{{/if}} {{/if}}
{{#if $eq status 'STOPPED'}} {{#if $eq status 'STOPPED'}}
<a href="#" onclick="trackLink('start container')" class="btn-icon btn-start" data-toggle="tooltip" data-placement="bottom" title="Start"><span class="typcn typcn-media-play-outline"></span></a> <a href="#" onclick="trackLink('start container')" class="btn-icon btn-start" data-toggle="tooltip" data-placement="bottom" title="Start"><span class="typcn typcn-media-play-outline"></span></a>

View File

@ -1,6 +1,7 @@
var remote = require('remote'); var remote = require('remote');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
var exec = require('child_process').exec; var exec = require('child_process').exec;
var path = require('path');
Template.dashboardSingleApp.rendered = function () { Template.dashboardSingleApp.rendered = function () {
Meteor.setInterval(function () { Meteor.setInterval(function () {
@ -48,22 +49,39 @@ Template.dashboardSingleApp.events({
}, },
'click .btn-restart': function (e) { 'click .btn-restart': function (e) {
e.preventDefault(); e.preventDefault();
AppUtil.restart(this._id); AppUtil.run(this, function (err) {});
}, },
'click .btn-folder': function (e) { 'click .btn-folder': function (e) {
e.preventDefault(); e.preventDefault();
var appId = this._id; var app = this;
var app = Apps.findOne(appId);
if (!app) { if (!app) {
throw new Error('Cannot find app with id: ' + appId); throw new Error('Cannot find app with id: ' + app._id);
} }
if (app.volumesEnabled) { var openDirectory = function () {
exec('open ' + this.path, function (err) { var appPath = path.join(Util.KITE_PATH, app.name);
if (app.docker.Volumes.length) {
if (app.docker.Volumes[0].Value.indexOf(path.join(Util.getHomePath(), 'Kitematic')) !== -1) {
exec('open ' + appPath, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
return; return;
} else {
exec('open ' + app.docker.Volumes[0].Value, function (err) {
if (err) { throw err; }
});
return;
}
} else {
exec('open ' + appPath, function (err) {
if (err) { throw err; }
});
}
};
if (app.volumesEnabled) {
openDirectory();
return;
} }
dialog.showMessageBox({ dialog.showMessageBox({
@ -71,14 +89,12 @@ Template.dashboardSingleApp.events({
buttons: ['Enable Volumes', 'Cancel'] buttons: ['Enable Volumes', 'Cancel']
}, function (index) { }, function (index) {
if (index === 0) { if (index === 0) {
Apps.update(appId, { Apps.update(app._id, {
$set: {volumesEnabled: true} $set: {volumesEnabled: true}
}); });
AppUtil.run(Apps.findOne(appId), function (err) { AppUtil.run(Apps.findOne(app._id), function (err) {
if (err) { throw err; } if (err) { throw err; }
exec('open ' + this.path, function (err) { openDirectory();
if (err) { throw err; }
});
}); });
} }
}); });

View File

@ -31,7 +31,6 @@ Template.modalCreateApp.events({
imageId: cleaned.imageId, imageId: cleaned.imageId,
status: 'STARTING', status: 'STARTING',
config: {}, config: {},
path: appPath,
logs: [], logs: [],
createdAt: new Date(), createdAt: new Date(),
volumesEnabled: true volumesEnabled: true

View File

@ -59,7 +59,7 @@ Template.modalCreateImage.events({
if (imageObj.meta.logo) { if (imageObj.meta.logo) {
Images.update(imageId, { Images.update(imageId, {
$set: { $set: {
logoPath: path.join(imagePath, imageObj.meta.logo) logoPath: path.join(directory, imageObj.meta.logo)
} }
}); });
} }

View File

@ -32,7 +32,8 @@ Template.dashboardAppsLayout.events({
}, },
'click .btn-folder': function () { 'click .btn-folder': function () {
var exec = require('child_process').exec; var exec = require('child_process').exec;
exec('open ' + this.path, function (err) { var appPath = path.join(Util.KITE_PATH, this.name);
exec('open ' + appPath, function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}, },

View File

@ -1,13 +1,13 @@
Apps = new Meteor.Collection('apps'); Apps = new Meteor.Collection('apps');
Apps.COMMON_WEB_PORTS = [ Apps.COMMON_WEB_PORTS = [
80, '80',
8000, '8000',
8080, '8080',
3000, '3000',
5000, '5000',
2368, '2368',
443 '443'
]; ];
Apps.allow({ Apps.allow({
@ -35,22 +35,27 @@ Apps.helpers({
if (!app.docker || !app.docker.NetworkSettings.Ports) { if (!app.docker || !app.docker.NetworkSettings.Ports) {
return []; return [];
} }
var results = _.map(app.docker.NetworkSettings.Ports, function (value, key) { var results = _.map(app.docker.NetworkSettings.Ports, function (value, key) {
var portProtocolPair = key.split('/'); var portProtocolPair = key.split('/');
var res = { var res = {
'port': parseInt(portProtocolPair[0]), 'port': portProtocolPair[0],
'protocol': portProtocolPair[1] 'protocol': portProtocolPair[1]
}; };
if (value.length) { if (value && value.length) {
var port = value[0].HostPort; var port = value[0].HostPort;
res['hostIp'] = Docker.hostIp; res['hostIp'] = Docker.hostIp;
res['hostPort'] = port; res['hostPort'] = port;
res['web'] = Apps.COMMON_WEB_PORTS.indexOf(res.port) !== -1; res['web'] = Apps.COMMON_WEB_PORTS.indexOf(res.port) !== -1;
res['url'] = 'http://' + Docker.hostIp + ':' + port; res['url'] = 'http://' + Docker.hostIp + ':' + port;
} else {
return null;
} }
return res; return res;
}); });
results = _.filter(results, function (res) { return res !== null; });
results.sort(function (a, b) { results.sort(function (a, b) {
// prefer lower ports // prefer lower ports
if (a.web && b.web) { if (a.web && b.web) {

View File

@ -12,12 +12,12 @@
"type": "GNU", "type": "GNU",
"url": "https://raw.githubusercontent.com/kitematic/kitematic/master/LICENSE" "url": "https://raw.githubusercontent.com/kitematic/kitematic/master/LICENSE"
}], }],
"boot2docker-version": "1.3.2",
"main": "index.js", "main": "index.js",
"version": "0.4.0", "version": "0.4.0",
"dependencies": { "dependencies": {
"ansi-to-html": "0.2.0", "ansi-to-html": "0.2.0",
"async": "^0.9.0", "async": "^0.9.0",
"chokidar": "git+https://github.com/usekite/chokidar.git",
"dockerode": "2.0.4", "dockerode": "2.0.4",
"exec": "^0.1.2", "exec": "^0.1.2",
"moment": "2.8.1", "moment": "2.8.1",
@ -25,6 +25,7 @@
"open": "0.0.5", "open": "0.0.5",
"request": "2.42.0", "request": "2.42.0",
"request-progress": "0.3.1", "request-progress": "0.3.1",
"tar": "0.1.20" "tar": "0.1.20",
"pkginfo": "0.3.0"
} }
} }

View File

@ -2,35 +2,31 @@
set -e # Auto exit on error set -e # Auto exit on error
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $DIR/colors.sh
BASE=$DIR/.. BASE=$DIR/..
NPM="$BASE/cache/node/bin/npm" NPM="$BASE/cache/node/bin/npm"
NODE="$BASE/cache/node/bin/node" NODE="$BASE/cache/node/bin/node"
VERSION=$($NODE -pe 'JSON.parse(process.argv[1]).version' "$(cat package.json)") VERSION=$($NODE -pe 'JSON.parse(process.argv[1]).version' "$(cat package.json)")
pushd $BASE/meteor source $DIR/colors.sh
$BASE/script/setup.sh $BASE/script/setup.sh
rm -rf ../bundle rm -rf ./bundle
cecho "-----> Building bundle from Meteor app, this may take a few minutes..." $blue cecho "-----> Building bundle from Meteor app, this may take a few minutes..." $blue
cd $BASE/meteor
meteor bundle --directory ../bundle meteor bundle --directory ../bundle
cd ../bundle cd $BASE/bundle
cecho "-----> Installing bundle npm packages." $blue cecho "-----> Installing bundle npm packages." $blue
pushd programs/server cd programs/server
$NPM install $NPM install
popd
cecho "Bundle created." $green cecho "Bundle created." $green
popd cd $BASE
pushd $BASE rm -Rf ./dist/osx/
rm -rf ./dist/osx
mkdir -p ./dist/osx/ mkdir -p ./dist/osx/
DIST_APP=Kitematic.app DIST_APP=Kitematic.app
@ -39,6 +35,7 @@ cecho "-----> Creating $DIST_APP..." $blue
find cache/atom-shell -name "debug\.log" -print0 | xargs -0 rm -rf find cache/atom-shell -name "debug\.log" -print0 | xargs -0 rm -rf
cp -R cache/atom-shell/Atom.app dist/osx/ cp -R cache/atom-shell/Atom.app dist/osx/
mv dist/osx/Atom.app dist/osx/$DIST_APP mv dist/osx/Atom.app dist/osx/$DIST_APP
mv dist/osx/$DIST_APP/Contents/MacOS/Atom dist/osx/$DIST_APP/Contents/MacOS/Kitematic
mkdir -p dist/osx/$DIST_APP/Contents/Resources/app mkdir -p dist/osx/$DIST_APP/Contents/Resources/app
cecho "-----> Copying meteor bundle into $DIST_APP..." $blue cecho "-----> Copying meteor bundle into $DIST_APP..." $blue
@ -58,7 +55,6 @@ cp kitematic.icns dist/osx/$DIST_APP/Contents/Resources/atom.icns
chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/terminal chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/terminal
chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/node chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/node
chmod -R u+w dist/osx/$DIST_APP/Contents/Resources/app/bundle chmod -R u+w dist/osx/$DIST_APP/Contents/Resources/app/bundle
cecho "-----> Updating Info.plist version to $VERSION" $blue cecho "-----> Updating Info.plist version to $VERSION" $blue
@ -66,19 +62,17 @@ cecho "-----> Updating Info.plist version to $VERSION" $blue
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName Kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName Kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleName Kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleName Kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.kitematic.kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.kitematic.kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable Kitematic" $BASE/dist/osx/$DIST_APP/Contents/Info.plist
if [ -f $DIR/sign.sh ]; then if [ -f $DIR/sign.sh ]; then
cecho "-----> Signing app file...." $blue cecho "-----> Signing app file...." $blue
$DIR/sign.sh $BASE/dist/osx/$DIST_APP $DIR/sign.sh $BASE/dist/osx/$DIST_APP
fi fi
pushd dist/osx cd $BASE/dist/osx
cecho "-----> Creating disributable zip file...." $blue cecho "-----> Creating disributable zip file...." $blue
ditto -c -k --sequesterRsrc --keepParent $DIST_APP Kitematic-$VERSION.zip ditto -c -k --sequesterRsrc --keepParent $DIST_APP Kitematic-$VERSION.zip
popd
cecho "Done." $green cecho "Done." $green
cecho "Kitematic app available at dist/osx/$DIST_APP" $green cecho "Kitematic app available at dist/osx/$DIST_APP" $green
cecho "Kitematic zip distribution available at dist/osx/Kitematic.zip" $green cecho "Kitematic zip distribution available at dist/osx/Kitematic.zip" $green
popd

View File

@ -1,22 +1,16 @@
#!/bin/bash #!/bin/bash
set -e # Auto exit on error set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $DIR/colors.sh
BASE=$DIR/..
pushd $BASE
mkdir -p cache
pushd cache
BOOT2DOCKER_CLI_VERSION=1.3.1
BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION
BOOT2DOCKER_CLI_FILE=boot2docker
ATOM_SHELL_VERSION=0.19.1 ATOM_SHELL_VERSION=0.19.1
ATOM_SHELL_FILE=atom-shell-v$ATOM_SHELL_VERSION-darwin-x64.zip ATOM_SHELL_FILE=atom-shell-v$ATOM_SHELL_VERSION-darwin-x64.zip
BASE=$DIR/..
source $DIR/colors.sh
cd $BASE
mkdir -p cache
cd cache
if [ ! -f $ATOM_SHELL_FILE ]; then if [ ! -f $ATOM_SHELL_FILE ]; then
cecho "-----> Downloading Atom Shell..." $purple cecho "-----> Downloading Atom Shell..." $purple
@ -42,9 +36,13 @@ if [ ! -f "node-v0.10.29-darwin-x64.tar.gz" ]; then
cp node/LICENSE $BASE/resources/NODE_LICENSE.txt cp node/LICENSE $BASE/resources/NODE_LICENSE.txt
fi fi
popd cd $BASE
pushd resources NODE="$BASE/cache/node/bin/node"
BOOT2DOCKER_CLI_VERSION=$($NODE -pe "JSON.parse(process.argv[1])['boot2dockerversion']" "$(cat package.json)")
BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION
cd resources
if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then
cecho "-----> Downloading Boot2docker CLI..." $purple cecho "-----> Downloading Boot2docker CLI..." $purple
@ -53,13 +51,12 @@ fi
chmod +x $BOOT2DOCKER_CLI_VERSION_FILE chmod +x $BOOT2DOCKER_CLI_VERSION_FILE
popd cd $BASE
# Build NPM modules
NPM="$BASE/cache/node/bin/npm" NPM="$BASE/cache/node/bin/npm"
export npm_config_disturl=https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist export npm_config_disturl=https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist
export npm_config_target=0.16.2 export npm_config_target=ATOM_SHELL_VERSION
export npm_config_arch=ia32 export npm_config_arch=ia32
HOME=~/.atom-shell-gyp $NPM install HOME=~/.atom-shell-gyp $NPM install
popd