Working ports update

This commit is contained in:
Jeff Morgan 2014-11-24 10:41:02 -05:00
parent 4ee1e4ecda
commit 54a123ef94
55 changed files with 470 additions and 347 deletions

View File

@ -124,10 +124,7 @@ 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();
@ -146,6 +143,10 @@ app.on('ready', function() {
exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {});
}); });
mainWindow.webContents.on('new-window', function (e) {
e.preventDefault();
});
mainWindow.webContents.on('did-finish-load', function() { mainWindow.webContents.on('did-finish-load', function() {
mainWindow.show(); mainWindow.show();
mainWindow.focus(); mainWindow.focus();

View File

@ -1 +1 @@
METEOR@0.9.4 METEOR@1.0

View File

@ -1,16 +1,16 @@
application-configuration@1.0.3 application-configuration@1.0.3
autoupdate@1.1.2 autoupdate@1.1.3
base64@1.0.1 base64@1.0.1
binary-heap@1.0.1 binary-heap@1.0.1
blaze-tools@1.0.1 blaze-tools@1.0.1
blaze@2.0.2 blaze@2.0.3
boilerplate-generator@1.0.1 boilerplate-generator@1.0.1
callback-hook@1.0.1 callback-hook@1.0.1
check@1.0.2 check@1.0.2
ctl-helper@1.0.4 ctl-helper@1.0.4
ctl@1.0.2 ctl@1.0.2
dburles:collection-helpers@1.0.1 dburles:collection-helpers@1.0.1
ddp@1.0.10 ddp@1.0.11
deps@1.0.5 deps@1.0.5
ejson@1.0.4 ejson@1.0.4
fastclick@1.0.1 fastclick@1.0.1
@ -18,23 +18,28 @@ follower-livedata@1.0.2
geojson-utils@1.0.1 geojson-utils@1.0.1
html-tools@1.0.2 html-tools@1.0.2
htmljs@1.0.2 htmljs@1.0.2
http@1.0.7 http@1.0.8
id-map@1.0.1 id-map@1.0.1
iron:core@0.3.4 iron:controller@1.0.2
iron:dynamic-template@0.4.1 iron:core@1.0.2
iron:layout@0.4.1 iron:dynamic-template@1.0.2
iron:router@0.9.4 iron:layout@1.0.2
iron:location@1.0.2
iron:middleware-stack@1.0.2
iron:router@1.0.2
iron:url@1.0.2
jquery@1.0.1 jquery@1.0.1
json@1.0.1 json@1.0.1
less@1.0.10 launch-screen@1.0.0
less@1.0.11
livedata@1.0.11 livedata@1.0.11
logging@1.0.4 logging@1.0.5
meteor-platform@1.1.2 meteor-platform@1.2.0
meteor@1.1.2 meteor@1.1.3
minifiers@1.1.1 minifiers@1.1.2
minimongo@1.0.4 minimongo@1.0.5
mobile-status-bar@1.0.1 mobile-status-bar@1.0.1
mongo@1.0.7 mongo@1.0.8
mrt:underscore-string-latest@2.3.3 mrt:underscore-string-latest@2.3.3
observe-sequence@1.0.3 observe-sequence@1.0.3
ordered-dict@1.0.1 ordered-dict@1.0.1
@ -44,17 +49,17 @@ reactive-dict@1.0.4
reactive-var@1.0.3 reactive-var@1.0.3
reload@1.1.1 reload@1.1.1
retry@1.0.1 retry@1.0.1
reywood:iron-router-ga@0.3.2 reywood:iron-router-ga@0.4.1
routepolicy@1.0.2 routepolicy@1.0.2
session@1.0.3 session@1.0.4
simison:bootstrap3-less@0.3.0 simison:bootstrap3-less@0.3.0
spacebars-compiler@1.0.3 spacebars-compiler@1.0.3
spacebars@1.0.3 spacebars@1.0.3
standard-app-packages@1.0.3 standard-app-packages@1.0.3
templating@1.0.8 templating@1.0.9
tracker@1.0.3 tracker@1.0.3
ui@1.0.4 ui@1.0.4
underscore@1.0.1 underscore@1.0.1
url@1.0.1 url@1.0.2
webapp-hashing@1.0.1 webapp-hashing@1.0.1
webapp@1.1.3 webapp@1.1.4

View File

@ -6,22 +6,21 @@ var convert = new Convert();
AppUtil = {}; AppUtil = {};
AppUtil.run = function (app) { 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 // Delete old container if one already exists
Docker.removeContainer(app.name, function (err) { Docker.removeContainer(app.name, function (err) {
if (err) { console.error(err); } if (err) { callback(err); }
Docker.runContainer(app, image, function (err, container) { Docker.runContainer(app, image, function (err, container) {
if (err) { throw err; } if (err) { callback(err); }
Docker.getContainerData(container.id, function (err, data) { Docker.getContainerData(container.id, function (err, data) {
if (err) { console.error(err); } if (err) { callback(err); }
// Set a delay for app to spin up // Set a delay for app to spin up
Meteor.setTimeout(function () { Apps.update(app._id, {$set: {
Apps.update(app._id, {$set: { docker: data,
docker: data, status: 'READY'
status: 'READY' }});
}}); callback();
}, 2500);
}); });
}); });
}); });
@ -29,7 +28,8 @@ AppUtil.run = function (app) {
AppUtil.restartHelper = function (app) { AppUtil.restartHelper = function (app) {
if (app.docker && app.docker.Id) { if (app.docker && app.docker.Id) {
Docker.restartContainer(app.docker.Id, function (err) { var container = Docker.client().getContainer(app.docker.Id);
container.restart(function (err) {
if (err) { console.error(err); } if (err) { console.error(err); }
Docker.getContainerData(app.docker.Id, function (err, data) { Docker.getContainerData(app.docker.Id, function (err, data) {
if (err) { console.error(err); } if (err) { console.error(err); }
@ -96,9 +96,6 @@ AppUtil.remove = function (appId) {
if (err) { console.error(err); } if (err) { console.error(err); }
var appPath = path.join(Util.KITE_PATH, app.name); var appPath = path.join(Util.KITE_PATH, app.name);
Util.deleteFolder(appPath); Util.deleteFolder(appPath);
Docker.removeBindFolder(app.name, function () {
console.log('Deleted Kite ' + app.name + ' directory.');
});
}); });
} }
}; };
@ -237,7 +234,8 @@ AppUtil.sync = function () {
config: config, config: config,
path: appPath, path: appPath,
logs: [], logs: [],
createdAt: new Date() createdAt: new Date(),
volumesEnabled: true
}; };
console.log(appObj); console.log(appObj);
Apps.insert(appObj); Apps.insert(appObj);

View File

@ -69,6 +69,12 @@ Boot2Docker.ip = function (callback) {
}); });
}; };
Boot2Docker.portOpen = function (port, callback) {
this.exec('nc -vz 127.0.0.1 ' + port, function (stderr, stdout, code) {
});
};
Boot2Docker.setIp = function (ifname, ip, callback) { Boot2Docker.setIp = function (ifname, ip, callback) {
Boot2Docker.exec('ssh "sudo ifconfig ' + ifname + ' ' + ip + ' netmask 255.255.255.0"', function (stderr, stdout) { Boot2Docker.exec('ssh "sudo ifconfig ' + ifname + ' ' + ip + ' netmask 255.255.255.0"', function (stderr, stdout) {
Boot2Docker.exec('ssh "sudo rm -rf /var/lib/boot2docker/tls/* && sudo /etc/init.d/docker restart"', function (stderr, stdout) { Boot2Docker.exec('ssh "sudo rm -rf /var/lib/boot2docker/tls/* && sudo /etc/init.d/docker restart"', function (stderr, stdout) {

View File

@ -95,7 +95,6 @@ Docker.runContainer = function (app, image, callback) {
var builtStr = key + '=' + app.config[key]; var builtStr = key + '=' + app.config[key];
envParam.push(builtStr); envParam.push(builtStr);
}); });
console.log(envParam);
Docker.client().createContainer({ Docker.client().createContainer({
Image: image.docker.Id, Image: image.docker.Id,
Tty: false, Tty: false,
@ -107,9 +106,9 @@ Docker.runContainer = function (app, image, callback) {
console.log('Created container: ' + container.id); console.log('Created container: ' + container.id);
// Bind volumes // Bind volumes
var binds = []; var binds = [];
if (image.docker.Config.Volumes && image.docker.Config.Volumes.length > 0) { if (app.volumesEnabled && image.docker.Config.Volumes && image.docker.Config.Volumes.length > 0) {
_.each(image.docker.Config.Volumes, function (vol) { _.each(image.docker.Config.Volumes, function (vol) {
binds.push('/var/lib/docker/binds/' + app.name + vol.Path + ':' + vol.Path); binds.push([Util.getHomePath(), 'Kitematic', app.name, vol.Path].join('/') + ':' + vol.Path);
}); });
} }
// Start the container // Start the container
@ -150,19 +149,6 @@ Docker.stopContainer = function (containerId, callback) {
}); });
}; };
Docker.restartContainer = function (containerId, callback) {
var container = Docker.client().getContainer(containerId);
container.restart(function (err) {
if (err) {
console.log(err);
callback(err);
return;
}
console.log('Restarted container: ' + containerId);
callback(null);
});
};
var convertVolumeObjToArray = function (obj) { var convertVolumeObjToArray = function (obj) {
var result = []; var result = [];
if (obj !== null && typeof obj === 'object') { if (obj !== null && typeof obj === 'object') {
@ -241,9 +227,3 @@ Docker.removeImage = function (imageId, callback) {
callback(null); callback(null);
}); });
}; };
Docker.removeBindFolder = function (name, callback) {
exec(Boot2Docker.command() + ' ssh "sudo rm -rf /var/lib/docker/binds/' + name + '"', function (err, stdout) {
callback(err, stdout);
});
};

View File

@ -6,15 +6,6 @@ var fs = require('fs');
ImageUtil = {}; ImageUtil = {};
var createTarFile = function (image, callback) {
var TAR_PATH = path.join(Util.KITE_TAR_PATH, image._id + '.tar');
exec('tar czf ' + TAR_PATH + ' -C ' + image.path + ' .', function (err) {
if (err) { callback(err, null); return; }
console.log('Created tar file: ' + TAR_PATH);
callback(null, TAR_PATH);
});
};
var getFromImage = function (dockerfile) { var getFromImage = function (dockerfile) {
var patternString = "(FROM)(.*)"; var patternString = "(FROM)(.*)";
var regex = new RegExp(patternString, "g"); var regex = new RegExp(patternString, "g");
@ -54,23 +45,8 @@ ImageUtil.getMetaData = function (directory) {
return kiteJSON; return kiteJSON;
}; };
ImageUtil.saveFolder = function (directory, imageId, callback) {
var destinationPath = path.join(Util.KITE_IMAGES_PATH, imageId);
if (!fs.existsSync(destinationPath)) {
fs.mkdirSync(destinationPath, function (err) {
if (err) { callback(err); return; }
});
Util.copyFolder(directory, destinationPath, function (err) {
if (err) { callback(err); return; }
console.log('Copied image folder for: ' + imageId);
callback(null);
});
}
};
ImageUtil.rebuildHelper = function (image, callback) { ImageUtil.rebuildHelper = function (image, callback) {
Util.deleteFolder(image.path); var imageMetaData = ImageUtil.getMetaData(image.path);
var imageMetaData = ImageUtil.getMetaData(image.originPath);
if (imageMetaData.logo) { if (imageMetaData.logo) {
Images.update(image._id, { Images.update(image._id, {
$set: { $set: {
@ -91,14 +67,11 @@ ImageUtil.rebuildHelper = function (image, callback) {
} }
}); });
image = Images.findOne(image._id); image = Images.findOne(image._id);
ImageUtil.saveFolder(image.originPath, image._id, function (err) { ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
if (err) { console.error(err); } if (err) { callback(err, null); return; }
ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) { ImageUtil.build(image, function (err) {
if (err) { callback(err, null); return; } if (err) { console.error(err); }
ImageUtil.build(image, function (err) { callback(null, null);
if (err) { console.error(err); }
callback(null, null);
});
}); });
}); });
}; };
@ -146,7 +119,6 @@ ImageUtil.pull = function (dockerfile, imageId, callback) {
console.log('From image: ' + fromImage); console.log('From image: ' + fromImage);
var installedImage = null; var installedImage = null;
Docker.getImageData(imageId, function (err, data) { Docker.getImageData(imageId, function (err, data) {
if (err) { console.error(err); }
installedImage = data; installedImage = data;
if (fromImage && !installedImage) { if (fromImage && !installedImage) {
Images.update(imageId, { Images.update(imageId, {
@ -191,7 +163,7 @@ ImageUtil.pull = function (dockerfile, imageId, callback) {
}; };
ImageUtil.build = function (image, callback) { ImageUtil.build = function (image, callback) {
createTarFile(image, function (err, tarFilePath) { Util.createTarFile(image.path, path.join(Util.KITE_TAR_PATH, image._id + '.tar'), function (err, tarFilePath) {
if (err) { console.error(err); } if (err) { console.error(err); }
Images.update(image._id, { Images.update(image._id, {
$set: { $set: {
@ -199,7 +171,7 @@ ImageUtil.build = function (image, callback) {
} }
}); });
Docker.client().buildImage(tarFilePath, {t: image.meta.name + ':' + image.meta.version}, function (err, response) { Docker.client().buildImage(tarFilePath, {t: image.meta.name + ':' + image.meta.version}, function (err, response) {
if (err) { callback(err); } if (err) { callback(err); return; }
console.log('Building Docker image...'); console.log('Building Docker image...');
response.setEncoding('utf8'); response.setEncoding('utf8');
response.on('data', function (data) { response.on('data', function (data) {
@ -265,11 +237,6 @@ ImageUtil.remove = function (imageId) {
if (err) { console.error(err); } if (err) { console.error(err); }
}); });
} }
try {
Util.deleteFolder(image.path);
} catch (e) {
console.error(e);
}
}; };
ImageUtil.sync = function () { ImageUtil.sync = function () {
@ -342,7 +309,6 @@ ImageUtil.sync = function () {
version: version version: version
} }
}; };
console.log(imageObj);
Images.insert(imageObj); Images.insert(imageObj);
} }
}); });

View File

@ -6,25 +6,26 @@ Router.configure({
var currentPath = Router.current().path; var currentPath = Router.current().path;
ga('send', 'pageview', currentPath); ga('send', 'pageview', currentPath);
} }
this.next();
} }
}); });
DashboardController = RouteController.extend({ DashboardController = RouteController.extend({
layoutTemplate: 'dashboard_layout', layoutTemplate: 'dashboardLayout',
waitOn: function () { waitOn: function () {
return [Meteor.subscribe('apps'), Meteor.subscribe('images'), Meteor.subscribe('settings')]; return [Meteor.subscribe('apps'), Meteor.subscribe('images'), Meteor.subscribe('settings')];
} }
}); });
AppController = DashboardController.extend({ AppController = DashboardController.extend({
layoutTemplate: 'dashboard_apps_layout', layoutTemplate: 'dashboardAppsLayout',
data: function () { data: function () {
return Apps.findOne({name: this.params.name}); return Apps.findOne({name: this.params.name});
} }
}); });
ImageController = DashboardController.extend({ ImageController = DashboardController.extend({
layoutTemplate: 'dashboard_images_layout', layoutTemplate: 'dashboardImagesLayout',
data: function () { data: function () {
return Images.findOne({_id: this.params.id}); return Images.findOne({_id: this.params.id});
} }
@ -50,6 +51,8 @@ Router.map(function () {
Settings.insert({tracking: true}); Settings.insert({tracking: true});
} }
Session.set('onIntro', false); Session.set('onIntro', false);
startUpdatingBoot2DockerUtilization();
// startSyncingAppState();
Router.go('dashboard_apps'); Router.go('dashboard_apps');
} }
}); });

View File

@ -101,10 +101,8 @@ Setup.steps = [
} }
}); });
}, },
message: 'Setting up the Docker VM...', message: 'Setting up the Docker VM...'
}, },
// Start the Docker VM
{ {
run: function (callback) { run: function (callback) {
Boot2Docker.state(function (err, state) { Boot2Docker.state(function (err, state) {
@ -118,9 +116,8 @@ Setup.steps = [
} }
}); });
}, },
message: 'Starting the Docker VM...', message: 'Starting the Docker VM...'
}, },
{ {
run: function (callback) { run: function (callback) {
Boot2Docker.ip(function (err, ip) { Boot2Docker.ip(function (err, ip) {

View File

@ -17,4 +17,11 @@ Meteor.startup(function () {
if (!fs.existsSync(Util.getResourceDir())) { if (!fs.existsSync(Util.getResourceDir())) {
fs.mkdirSync(Util.getResourceDir()); fs.mkdirSync(Util.getResourceDir());
} }
Boot2Docker.ip(function (err, ip) {
if (!err) {
console.log('Setting host IP to: ' + ip);
Docker.setHost(ip);
}
});
}); });

View File

@ -77,6 +77,14 @@ Util.copyVolumes = function (directory, appName, callback) {
} }
}; };
Util.createTarFile = function (sourcePath, destinationFile, callback) {
exec('tar czf ' + destinationFile + ' -C ' + sourcePath + ' .', function (err) {
if (err) {callback(err, null); return;}
console.log('Created tar file: ' + destinationFile);
callback(null, destinationFile);
});
};
Util.hasDockerfile = function (directory) { Util.hasDockerfile = function (directory) {
return fs.existsSync(path.join(directory, 'Dockerfile')); return fs.existsSync(path.join(directory, 'Dockerfile'));
}; };

View File

@ -69,50 +69,41 @@ Handlebars.registerHelper('displayTags', function (tags, delimiter) {
} }
}); });
var fixBoot2DockerVM = function (callback) { updateBoot2DockerUtilization = function (callback) {
Boot2Docker.check(function (err) { Boot2Docker.exists(function (err, exists) {
if (err) { if (err) { callback(err); return; }
Session.set('available', false); if (exists) {
Boot2Docker.resolve(function (err) { Boot2Docker.state(function (err, state) {
if (err) { if (err) { callback(err); return; }
callback(err); Session.set('boot2dockerState', state);
if (state === 'running') {
Boot2Docker.stats(function (err, stats) {
if (err) { callback(err); return; }
if (stats.state !== 'poweroff' && stats.memory && stats.disk) {
Session.set('boot2dockerMemoryUsage', stats.memory);
Session.set('boot2dockerDiskUsage', stats.disk);
callback();
}
});
} else { } else {
Session.set('available', true);
callback(); callback();
} }
}); });
} else {
callback();
} }
}); });
}; };
Meteor.setInterval(function () { startUpdatingBoot2DockerUtilization = function () {
if (!Session.get('onIntro')) { updateBoot2DockerUtilization(function (err) {
Boot2Docker.exists(function (err, exists) { Meteor.setTimeout(updateBoot2DockerUtilization, 5000);
if (err) { console.log(err); return; } });
if (exists) { };
Boot2Docker.state(function (err, state) {
if (err) { console.log(err); return; }
Session.set('boot2dockerState', state);
if (state === 'running') {
Boot2Docker.stats(function (err, stats) {
if (err) { console.log(err); return; }
if (stats.state !== 'poweroff' && stats.memory && stats.disk) {
Session.set('boot2dockerMemoryUsage', stats.memory);
Session.set('boot2dockerDiskUsage', stats.disk);
}
});
}
});
}
});
}
}, 5000);
Meteor.setInterval(function () { startSyncingAppState = function () {
if (!Session.get('onIntro')) { ImageUtil.sync();
AppUtil.sync();
Meteor.setTimeout(function () {
ImageUtil.sync(); ImageUtil.sync();
AppUtil.sync(); AppUtil.sync();
} }, 5000);
}, 5000); };

View File

@ -20,6 +20,49 @@
.btn-icon { .btn-icon {
font-size: 20px; font-size: 20px;
margin-left: 0.4em; margin-left: 0.4em;
cursor: pointer;
}
.dropdown {
&.open {
.tooltip {
display: none !important;
}
}
}
.ports {
.dropdown-menu {
min-width: 241px;
}
.btn-group {
top: -2px;
}
.btn-globe {
line-height: 22px;
padding: 0 5px;
.typcn {
font-size: 18px;
top: 2px;
position: relative;
}
}
.btn-ports {
.caret {
margin-left: 3px;
margin-right: 1px;
margin-top: -2px
}
}
.btn-caret {
padding-top: 3px;
padding-bottom: 3px;
border-width: 1px;
padding-left: 3px;
padding-right: 3px;
}
&.open + .tooltip {
display: none !important;
}
position: relative;
} }
} }
&:hover { &:hover {
@ -259,6 +302,39 @@
} }
} }
.app-ports {
cursor: default;
padding: 10px 15px 3px;
min-width: 240px;
li {
padding-bottom: 7px;
white-space: nowrap;
.port-wrapper {
display: inline-block;
min-width: 72px;
font-weight: bold;
font-size: 13px;
}
.arrow-wrapper {
text-align: center;
display: inline-block;
min-width: 28px;
font-size: 15px;
color: #999;
}
.open-button-wrapper {
display: inline-block;
}
.host-address-wrapper {
font-family: Monaco, monospace;
font-size: 12px;
cursor: text;
.text-select();
}
}
}
.utilization { .utilization {
font-size: 12px; font-size: 12px;
} }

View File

@ -1,4 +1,4 @@
<template name="dashboard_apps_logs"> <template name="dashboardAppsLogs">
<div class="logs"> <div class="logs">
{{#each logs}} {{#each logs}}
<h6>{{{this}}}</h6> <h6>{{{this}}}</h6>

View File

@ -0,0 +1,23 @@
<template name="dashboardAppsPorts">
<div class="app-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">
<span class="port-wrapper">Port {{this.port}}</span>
<span class="arrow-wrapper">
<span class="typcn typcn-arrow-right"></span>
</span>
<span class="host-address-wrapper">{{this.hostIp}}:{{this.hostPort}}</span>
</li>
{{/if}}
{{/each}}
</div>
</template>

View File

@ -1,41 +1,7 @@
<template name="dashboard_apps_settings"> <template name="dashboardAppsSettings">
<div class="section"> <div class="section">
<div class="left-section"> <div class="left-section">
<h4>Container Details</h4> <h4>Environment Variables</h4>
<p class="help-block">This section lists more information about this container.</p>
</div>
<div class="right-section">
<div class="row">
<div class="col-xs-3">
<label>Host URL</label>
</div>
<div class="col-xs-8">
{{url}}
</div>
</div>
{{#if ports}}
<div class="row">
<div class="col-xs-3">
<label>Ports</label>
</div>
<div class="col-xs-8">
{{ports}}
</div>
</div>
{{/if}}
<div class="row">
<div class="col-xs-3">
<label>Status</label>
</div>
<div class="col-xs-8">
{{status}}
</div>
</div>
</div>
</div>
<div class="section">
<div class="left-section">
<h4>Config Variables</h4>
<p class="help-block">You can update your container's environment variables here.</p> <p class="help-block">You can update your container's environment variables here.</p>
</div> </div>
<div class="right-section"> <div class="right-section">
@ -69,6 +35,19 @@
</form> </form>
</div> </div>
</div> </div>
<div class="section">
<div class="left-section">
<h4>Volumes</h4>
<p class="help-block">When enabled, this container's volume data will be available under ~/Kitematic/{{name}}.</p>
</div>
<div class="right-section">
{{#if volumesEnabled}}
<a onclick="trackLink('enable container volumes')" class="btn btn-negative btn-disable-volumes btn-sm">Disable Volumes</a>
{{else}}
<a onclick="trackLink('disable container volumes')" class="btn btn-positive btn-enable-volumes btn-sm">Enable Volumes</a>
{{/if}}
</div>
</div>
<div class="section"> <div class="section">
<div class="left-section"> <div class="left-section">
<h4>Delete Container</h4> <h4>Delete Container</h4>

View File

@ -13,7 +13,7 @@ var getConfigVars = function ($form) {
return configVars; return configVars;
}; };
Template.dashboard_apps_settings.events({ Template.dashboardAppsSettings.events({
'click .btn-delete-var': function (e) { 'click .btn-delete-var': function (e) {
var $button = $(e.currentTarget); var $button = $(e.currentTarget);
$button.attr("disabled", "disabled"); $button.attr("disabled", "disabled");
@ -41,7 +41,8 @@ Template.dashboard_apps_settings.events({
e.stopPropagation(); e.stopPropagation();
return false; return false;
}, },
'click .btn-delete-app': function () { 'click .btn-delete-app': function (e) {
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 app?',
@ -52,5 +53,29 @@ Template.dashboard_apps_settings.events({
Router.go('dashboard_apps'); Router.go('dashboard_apps');
} }
}); });
},
'click .btn-enable-volumes': function (e) {
e.preventDefault();
var appId = this._id;
Apps.update(appId, {
$set: {volumesEnabled: true}
});
AppUtil.run(Apps.findOne(appId), function (err) {
if (err) {
throw err;
}
});
},
'click .btn-disable-volumes': function (e) {
e.preventDefault();
var appId = this._id;
Apps.update(appId, {
$set: {volumesEnabled: false}
});
AppUtil.run(Apps.findOne(appId), function (err) {
if (err) {
throw err;
}
});
} }
}); });

View File

@ -1,4 +1,4 @@
<template name="dashboard_apps"> <template name="dashboardApps">
<div class="header"> <div class="header">
<h3>Containers</h3> <h3>Containers</h3>
<div class="options"> <div class="options">
@ -14,7 +14,7 @@
{{#if hasItem apps}} {{#if hasItem apps}}
<div class="apps line-item-collection"> <div class="apps line-item-collection">
{{#each apps}} {{#each apps}}
{{> dashboard_single_app}} {{> dashboardSingleApp}}
{{/each}} {{/each}}
</div> </div>
{{else}} {{else}}
@ -30,6 +30,6 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{> modal_create_app}} {{> modalCreateApp}}
{{> modal_create_image}} {{> modalCreateImage}}
</template> </template>

View File

@ -1,4 +1,4 @@
Template.dashboard_apps.helpers({ Template.dashboardApps.helpers({
apps: function () { apps: function () {
return Apps.find({}, {sort: {createdAt: -1}}); return Apps.find({}, {sort: {createdAt: -1}});
} }

View File

@ -0,0 +1,18 @@
Template.dashboardSingleApp.events({
'click .btn-view-port': function (e) {
try {
var open = require('open');
e.preventDefault();
e.stopPropagation();
var $btn = $(e.currentTarget);
var url = $btn.attr('href');
open(url);
} catch (exception) {
console.log(exception);
}
},
'click .host-address-wrapper': function (e) {
e.preventDefault();
e.stopPropagation();
}
});

View File

@ -1,4 +1,4 @@
<template name="dashboard_single_app"> <template name="dashboardSingleApp">
<div class="app"> <div class="app">
<h5> <h5>
{{#if $eq status 'READY'}} {{#if $eq status 'READY'}}
@ -23,19 +23,42 @@
</h5> </h5>
<div class="options"> <div class="options">
{{#if $eq status 'READY'}} {{#if $eq status 'READY'}}
{{#if url}} {{#if viewPort}}
<a href="{{url}}" onclick="trackLink('view container')" class="btn-icon btn-view" target="_blank" data-toggle="tooltip" data-placement="bottom" title="View"><span class="typcn typcn-eye-outline"></span></a> <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">
<span class="typcn typcn-world-outline"></span>
</a>
<a href="#" class="btn btn-action btn-xs btn-caret dropdown-toggle" id="dashboardAppsPorts" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</a>
<ul class="dropdown-menu pull-right" role="menu" aria-labelledby="dashboardAppsPorts">
{{> dashboardAppsPorts}}
</ul>
</div>
{{else}}
<span class="ports dropdown">
<div class="dropdown btn-group btn-icon dropdown-toggle" id="dashboardAppsPorts" data-toggle="dropdown" data-placement="bottom" title="View">
<a href="#" onclick="trackLink('view container')" class="btn btn-action btn-xs btn-globe btn-ports">
<span class="typcn typcn-world-outline"></span>
<span class="caret"></span>
</a>
</div>
<ul class="dropdown-menu pull-right" role="menu" aria-labelledby="dashboardAppsPorts">
{{> dashboardAppsPorts}}
</ul>
</span>
{{/if}} {{/if}}
<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>
{{/if}} {{/if}}
<a href="#" onclick="trackLink('open container folder')" class="btn-icon btn-folder" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Folder"><span class="typcn typcn-folder-open"></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('stop container')" class="btn-icon btn-stop" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Stop"><span class="typcn typcn-media-pause-outline"></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>
{{/if}} {{/if}}
{{#if $eq status 'STOPPED'}} {{#if $eq status 'STOPPED'}}
<a href="#" onclick="trackLink('start container')" class="btn-icon btn-start" target="_blank" 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>
{{/if}} {{/if}}
<a href="#" onclick="trackLink('restart container')" class="btn-icon btn-restart" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Restart"><span class="typcn typcn-refresh-outline"></span></a> <a href="#" onclick="trackLink('restart container')" class="btn-icon btn-restart" data-toggle="tooltip" data-placement="bottom" title="Restart"><span class="typcn typcn-refresh-outline"></span></a>
<a href="/apps/{{name}}/logs" onclick="trackLink('container logs')" class="btn-icon btn-logs" data-toggle="tooltip" data-placement="bottom" title="Logs"><span class="typcn typcn-document-text"></span></a> <a href="/apps/{{name}}/logs" onclick="trackLink('container logs')" class="btn-icon btn-logs" data-toggle="tooltip" data-placement="bottom" title="Logs"><span class="typcn typcn-document-text"></span></a>
<a href="/apps/{{name}}/settings" onclick="trackLink('container settings')" class="btn-icon" data-toggle="tooltip" data-placement="bottom" title="Settings"><span class="typcn typcn-cog-outline"></span></a> <a href="/apps/{{name}}/settings" onclick="trackLink('container settings')" class="btn-icon" data-toggle="tooltip" data-placement="bottom" title="Settings"><span class="typcn typcn-cog-outline"></span></a>
</div> </div>

View File

@ -1,10 +1,24 @@
Template.dashboard_single_app.rendered = function () { var remote = require('remote');
var dialog = remote.require('dialog');
var exec = require('child_process').exec;
Template.dashboardSingleApp.rendered = function () {
Meteor.setInterval(function () { Meteor.setInterval(function () {
$('.btn-icon').tooltip(); $('.btn-icon').tooltip();
}, 1000); }, 1000);
}; };
Template.dashboard_single_app.events({ Template.dashboardSingleApp.helpers({
viewPort: function () {
var ports = this.ports();
if (ports[0] && ports[0].web) {
return ports[0];
}
return null;
}
});
Template.dashboardSingleApp.events({
'click .btn-view': function (e) { 'click .btn-view': function (e) {
try { try {
var open = require('open'); var open = require('open');
@ -22,24 +36,54 @@ Template.dashboard_single_app.events({
var cmd = Boot2Docker.command() + ' ssh -t "sudo docker exec -i -t ' + app.docker.Id + ' bash"'; var cmd = Boot2Docker.command() + ' ssh -t "sudo docker exec -i -t ' + app.docker.Id + ' bash"';
Util.openTerminal(cmd); Util.openTerminal(cmd);
}, },
'click .btn-start': function () { 'click .btn-start': function (e) {
e.preventDefault();
AppUtil.start(this._id); AppUtil.start(this._id);
$('.btn-icon').tooltip('hide'); $('.btn-icon').tooltip('hide');
}, },
'click .btn-stop': function () { 'click .btn-stop': function (e) {
e.preventDefault();
AppUtil.stop(this._id); AppUtil.stop(this._id);
$('.btn-icon').tooltip('hide'); $('.btn-icon').tooltip('hide');
}, },
'click .btn-restart': function () { 'click .btn-restart': function (e) {
e.preventDefault();
AppUtil.restart(this._id); AppUtil.restart(this._id);
}, },
'click .btn-folder': function () { 'click .btn-folder': function (e) {
var exec = require('child_process').exec; e.preventDefault();
exec('open ' + this.path, function (err) { var appId = this._id;
if (err) { throw err; }
var app = Apps.findOne(appId);
if (!app) {
throw new Error('Cannot find app with id: ' + appId);
}
if (app.volumesEnabled) {
exec('open ' + this.path, function (err) {
if (err) { throw err; }
});
return;
}
dialog.showMessageBox({
message: 'Volumes need to be enabled to view their contents via Finder. Enable volumes for this container?',
buttons: ['Enable Volumes', 'Cancel']
}, function (index) {
if (index === 0) {
Apps.update(appId, {
$set: {volumesEnabled: true}
});
AppUtil.run(Apps.findOne(appId), function (err) {
if (err) { throw err; }
exec('open ' + this.path, function (err) {
if (err) { throw err; }
});
});
}
}); });
}, },
'click .btn-logs': function () { 'click .btn-logs': function (e) {
AppUtil.logs(this._id); AppUtil.logs(this._id);
} }
}); });

View File

@ -1,4 +1,4 @@
<template name="dashboard_menu"> <template name="dashboardMenu">
<div class="dashboard-menu"> <div class="dashboard-menu">
<div class="mac-window-options"> <div class="mac-window-options">
<div class="mac-close"><i class="fa fa-times no-display"></i></div> <div class="mac-close"><i class="fa fa-times no-display"></i></div>

View File

@ -1,6 +1,6 @@
var remote = require('remote'); var remote = require('remote');
Template.dashboard_menu.events({ Template.dashboardMenu.events({
'click .mac-close': function () { 'click .mac-close': function () {
remote.getCurrentWindow().hide(); remote.getCurrentWindow().hide();
}, },
@ -19,7 +19,7 @@ Template.dashboard_menu.events({
} }
}); });
Template.dashboard_menu.rendered = function () { Template.dashboardMenu.rendered = function () {
$('.nav a').attr('tabIndex', '-1'); $('.nav a').attr('tabIndex', '-1');
$('.nav a').attr('onfocus', 'this.blur()'); $('.nav a').attr('onfocus', 'this.blur()');
$('.nav a').tooltip(); $('.nav a').tooltip();

View File

@ -1,4 +1,4 @@
<template name="menu_header"> <template name="menuHeader">
<div class="mac-window-header"><a href="#">Kitematic</a></div> <div class="mac-window-header"><a href="#">Kitematic</a></div>
{{> update_notification}} {{> updateNotification}}
</template> </template>

View File

@ -1,4 +1,4 @@
<template name="modal_create_app"> <template name="modalCreateApp">
<div class="modal fade" id="modal-create-app" tabindex="-1" role="dialog" aria-labelledby="modal-create-app" aria-hidden="true"> <div class="modal fade" id="modal-create-app" tabindex="-1" role="dialog" aria-labelledby="modal-create-app" aria-hidden="true">
<div class="modal-dialog modal-small"> <div class="modal-dialog modal-small">
<div class="modal-content"> <div class="modal-content">

View File

@ -1,13 +1,13 @@
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
Template.modal_create_app.helpers({ Template.modalCreateApp.helpers({
images: function () { images: function () {
return Images.find({status: 'READY', 'docker.Config.ExposedPorts': {$ne: null}}, {sort: {createdAt: -1}}); return Images.find({status: 'READY', 'docker.Config.ExposedPorts': {$ne: null}}, {sort: {createdAt: -1}});
} }
}); });
Template.modal_create_app.events({ Template.modalCreateApp.events({
'submit #form-create-app': function (e) { 'submit #form-create-app': function (e) {
var $form = $(e.currentTarget); var $form = $(e.currentTarget);
var formData = $form.serializeObject(); var formData = $form.serializeObject();
@ -33,18 +33,16 @@ Template.modal_create_app.events({
config: {}, config: {},
path: appPath, path: appPath,
logs: [], logs: [],
createdAt: new Date() createdAt: new Date(),
volumesEnabled: true
}; };
var appId = Apps.insert(appObj); var appId = Apps.insert(appObj);
var app = Apps.findOne(appId); var app = Apps.findOne(appId);
var image = Images.findOne(app.imageId); var image = Images.findOne(app.imageId);
Util.copyVolumes(image.path, app.name, function (err) { Util.copyVolumes(image.path, app.name, function (err) {
if (err) { console.error(err); } if (err) { console.error(err); }
Docker.removeBindFolder(app.name, function (err) { AppUtil.run(app, function (err) {
if (err) { console.error(err); } if (err) { console.error(err); }
AppUtil.run(app, function (err) {
if (err) { console.error(err); }
});
}); });
}); });
$('#modal-create-app').bind('hidden.bs.modal', function () { $('#modal-create-app').bind('hidden.bs.modal', function () {

View File

@ -1,4 +1,4 @@
<template name="modal_create_image"> <template name="modalCreateImage">
<div class="modal fade" id="modal-create-image" tabindex="-1" role="dialog" aria-labelledby="modal-create-image" aria-hidden="true"> <div class="modal fade" id="modal-create-image" tabindex="-1" role="dialog" aria-labelledby="modal-create-image" aria-hidden="true">
<div class="modal-dialog modal-small"> <div class="modal-dialog modal-small">
<div class="modal-content"> <div class="modal-content">

View File

@ -3,13 +3,13 @@ var fs = require('fs');
var remote = require('remote'); var remote = require('remote');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
Template.modal_create_image.rendered = function () { Template.modalCreateImage.rendered = function () {
$('#modal-create-image').bind('hidden.bs.modal', function () { $('#modal-create-image').bind('hidden.bs.modal', function () {
Router.go('dashboard_images'); Router.go('dashboard_images');
}); });
}; };
Template.modal_create_image.events({ Template.modalCreateImage.events({
'click #btn-pick-directory': function () { 'click #btn-pick-directory': function () {
dialog.showOpenDialog({properties: ['openDirectory']}, function (filenames) { dialog.showOpenDialog({properties: ['openDirectory']}, function (filenames) {
if (!filenames) { if (!filenames) {
@ -40,10 +40,9 @@ Template.modal_create_image.events({
$('#picked-directory-error').html(''); $('#picked-directory-error').html('');
$('#picked-directory').html(''); $('#picked-directory').html('');
$('#btn-create-image').attr('disabled', 'disabled'); $('#btn-create-image').attr('disabled', 'disabled');
$('#modal-create-image').modal('hide');
var imageObj = { var imageObj = {
status: 'BUILDING', status: 'BUILDING',
originPath: directory, path: directory,
buildLogs: [], buildLogs: [],
createdAt: new Date() createdAt: new Date()
}; };
@ -51,12 +50,12 @@ Template.modal_create_image.events({
imageObj.meta = imageMetaData; imageObj.meta = imageMetaData;
imageObj.tags = [imageMetaData.name + ':' + imageMetaData.version]; imageObj.tags = [imageMetaData.name + ':' + imageMetaData.version];
var imageId = Images.insert(imageObj); var imageId = Images.insert(imageObj);
var imagePath = path.join(Util.KITE_IMAGES_PATH, imageId);
Images.update(imageId, { $('#modal-create-image').modal('hide');
$set: { $('#modal-create-image').on('hidden.bs.modal', function () {
path: imagePath Router.go('dashboard_images_logs', {id: imageId});
}
}); });
if (imageObj.meta.logo) { if (imageObj.meta.logo) {
Images.update(imageId, { Images.update(imageId, {
$set: { $set: {
@ -65,13 +64,10 @@ Template.modal_create_image.events({
}); });
} }
var image = Images.findOne(imageId); var image = Images.findOne(imageId);
ImageUtil.saveFolder(image.originPath, imageId, function (err) { ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) {
if (err) { console.error(err); } if (err) { throw err; }
ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), imageId, function (err) { ImageUtil.build(image, function (err) {
if (err) { throw err; } if (err) { console.error(err); }
ImageUtil.build(image, function (err) {
if (err) { console.error(err); }
});
}); });
}); });
} }

View File

@ -1,14 +1,14 @@
Handlebars.registerHelper('activeDashboardMenuItem', function (page) { Handlebars.registerHelper('activeDashboardMenuItem', function (page) {
var currentPage = Router.current(true).path.split('/')[1]; var currentPage = Router.current(true).url.split('/')[1];
return page === currentPage ? 'active' : ''; return page === currentPage ? 'active' : '';
}); });
Handlebars.registerHelper('activeDashboardSubMenuItem', function (page) { Handlebars.registerHelper('activeDashboardSubMenuItem', function (page) {
var currentPage = Router.current(true).path.split('/')[3]; var currentPage = Router.current(true).url.split('/')[3];
return page === currentPage ? 'active' : ''; return page === currentPage ? 'active' : '';
}); });
Handlebars.registerHelper('currentDashboardPage', function () { Handlebars.registerHelper('currentDashboardPage', function () {
var currentPage = Router.current(true).path.split('/')[1]; var currentPage = Router.current(true).url.split('/')[1];
return currentPage; return currentPage;
}); });

View File

@ -1,4 +1,4 @@
<template name="dashboard_images_logs"> <template name="dashboardImagesLogs">
<div class="download-status"> <div class="download-status">
<div class="progress"> <div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: {{this.downloadPercentage}};"> <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: {{this.downloadPercentage}};">

View File

@ -1,4 +1,4 @@
<template name="dashboard_images_settings"> <template name="dashboardImagesSettings">
<div class="section"> <div class="section">
<div class="left-section"> <div class="left-section">
<h4>Image Details</h4> <h4>Image Details</h4>

View File

@ -1,7 +1,7 @@
var remote = require('remote'); var remote = require('remote');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
Template.dashboard_images_settings.events({ Template.dashboardImagesSettings.events({
'click .btn-delete-image': function () { 'click .btn-delete-image': function () {
var imageId = this._id; var imageId = this._id;
dialog.showMessageBox({ dialog.showMessageBox({

View File

@ -1,4 +1,4 @@
<template name="dashboard_images"> <template name="dashboardImages">
<div class="header"> <div class="header">
<h3>Images</h3> <h3>Images</h3>
<div class="options"> <div class="options">
@ -14,7 +14,7 @@
{{#if hasItem images}} {{#if hasItem images}}
<div class="images line-item-collection"> <div class="images line-item-collection">
{{#each images}} {{#each images}}
{{> dashboard_single_image}} {{> dashboardSingleImage}}
{{/each}} {{/each}}
</div> </div>
{{else}} {{else}}
@ -30,6 +30,6 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{> modal_create_app}} {{> modalCreateApp}}
{{> modal_create_image}} {{> modalCreateImage}}
</template> </template>

View File

@ -1,4 +1,4 @@
Template.dashboard_images.helpers({ Template.dashboardImages.helpers({
images: function () { images: function () {
return Images.find({}, {sort: {createdAt: -1}}); return Images.find({}, {sort: {createdAt: -1}});
} }

View File

@ -1,4 +1,4 @@
<template name="dashboard_single_image"> <template name="dashboardSingleImage">
<div class="image"> <div class="image">
<h5> <h5>
{{#if $eq status 'READY'}} {{#if $eq status 'READY'}}
@ -35,7 +35,7 @@
<a onclick="trackLink('open image folder')" href="#" class="btn-icon btn-folder" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Folder"><span class="typcn typcn-folder-open"></span></a> <a onclick="trackLink('open image folder')" href="#" class="btn-icon btn-folder" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Folder"><span class="typcn typcn-folder-open"></span></a>
{{/if}} {{/if}}
{{#if $neq status 'BUILDING'}} {{#if $neq status 'BUILDING'}}
{{#if originPath}} {{#if $or path originPath}}
<a onclick="trackLink('rebuild image')" href="#" class="btn-icon btn-rebuild" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Rebuild"><span class="typcn typcn-refresh-outline"></span></a> <a onclick="trackLink('rebuild image')" href="#" class="btn-icon btn-rebuild" target="_blank" data-toggle="tooltip" data-placement="bottom" title="Rebuild"><span class="typcn typcn-refresh-outline"></span></a>
{{/if}} {{/if}}
{{/if}} {{/if}}

View File

@ -1,10 +1,10 @@
Template.dashboard_single_image.rendered = function () { Template.dashboardSingleImage.rendered = function () {
Meteor.setInterval(function () { Meteor.setInterval(function () {
$('.btn-icon').tooltip(); $('.btn-icon').tooltip();
}, 1000); }, 1000);
}; };
Template.dashboard_single_image.events({ Template.dashboardSingleImage.events({
'click .btn-create-app': function () { 'click .btn-create-app': function () {
$('#modal-create-app').modal('show'); $('#modal-create-app').modal('show');
$('#form-create-app').find('input[name="imageId"]').val(this._id); $('#form-create-app').find('input[name="imageId"]').val(this._id);
@ -16,7 +16,8 @@ Template.dashboard_single_image.events({
if (err) { throw err; } if (err) { throw err; }
}); });
}, },
'click .btn-rebuild': function () { 'click .btn-rebuild': function (e) {
e.preventDefault();
$('.btn-icon').tooltip('hide'); $('.btn-icon').tooltip('hide');
ImageUtil.rebuild(this._id, function (err) { ImageUtil.rebuild(this._id, function (err) {
if (err) { console.error(err); } if (err) { console.error(err); }

View File

@ -4,13 +4,13 @@
<div class="container text-left"> <div class="container text-left">
<div class="content"> <div class="content">
{{#if currentSetupFailedError}} {{#if currentSetupFailedError}}
{{> radial_progress progress=100 class="radial-negative"}} {{> radialProgress progress=100 class="radial-negative"}}
<p>Error: {{currentSetupFailedError}}</p> <p>Error: {{currentSetupFailedError}}</p>
{{else}} {{else}}
{{#if currentSetupStepProgress}} {{#if currentSetupStepProgress}}
{{> radial_progress progress=currentSetupStepProgress}} {{> radialProgress progress=currentSetupStepProgress}}
{{else}} {{else}}
{{> radial_spinner }} {{> radialSpinner }}
{{/if}} {{/if}}
<p>{{currentSetupStepMessage}}</p> <p>{{currentSetupStepMessage}}</p>
{{/if}} {{/if}}

View File

@ -1,12 +1,12 @@
<template name="dashboard_apps_layout"> <template name="dashboardAppsLayout">
{{setTitle this.name}} {{setTitle this.name}}
<input id="appId" type="hidden" value="{{this._id}}"> <input id="appId" type="hidden" value="{{this._id}}">
<div class="dashboard"> <div class="dashboard">
<div class="container"> <div class="container">
<div class="dashboard-row"> <div class="dashboard-row">
{{> dashboard_menu}} {{> dashboardMenu}}
<div class="dashboard-body"> <div class="dashboard-body">
{{> menu_header}} {{> menuHeader}}
<div class="header"> <div class="header">
<h3> <h3>
<a href="/apps" onclick="trackLink('back to containers')">Containers</a> &raquo; {{this.name}} <a href="/apps" onclick="trackLink('back to containers')">Containers</a> &raquo; {{this.name}}

View File

@ -1,10 +1,10 @@
Template.dashboard_apps_layout.rendered = function () { Template.dashboardAppsLayout.rendered = function () {
Meteor.setInterval(function () { Meteor.setInterval(function () {
$('.header .icons a').tooltip(); $('.header .icons a').tooltip();
}, 1000); }, 1000);
}; };
Template.dashboard_apps_layout.events({ Template.dashboardAppsLayout.events({
'click .btn-view': function (e) { 'click .btn-view': function (e) {
try { try {
var open = require('open'); var open = require('open');

View File

@ -1,12 +1,12 @@
<template name="dashboard_images_layout"> <template name="dashboardImagesLayout">
{{setTitle this.meta.name}} {{setTitle this.meta.name}}
<input id="imageId" type="hidden" value="{{this._id}}"> <input id="imageId" type="hidden" value="{{this._id}}">
<div class="dashboard"> <div class="dashboard">
<div class="container"> <div class="container">
<div class="dashboard-row"> <div class="dashboard-row">
{{> dashboard_menu}} {{> dashboardMenu}}
<div class="dashboard-body"> <div class="dashboard-body">
{{> menu_header}} {{> menuHeader}}
<div class="header"> <div class="header">
<h3> <h3>
<a href="/images" onclick="trackLink('back to images')">Images</a> &raquo; {{this.meta.name}} <a href="/images" onclick="trackLink('back to images')">Images</a> &raquo; {{this.meta.name}}
@ -37,6 +37,6 @@
</div> </div>
</div> </div>
</div> </div>
{{> modal_create_app}} {{> modalCreateApp}}
</div> </div>
</template> </template>

View File

@ -1,10 +1,10 @@
Template.dashboard_images_layout.rendered = function () { Template.dashboardImagesLayout.rendered = function () {
Meteor.setInterval(function () { Meteor.setInterval(function () {
$('.header .icons a').tooltip(); $('.header .icons a').tooltip();
}, 1000); }, 1000);
}; };
Template.dashboard_images_layout.events({ Template.dashboardImagesLayout.events({
'click .btn-create-app': function () { 'click .btn-create-app': function () {
$('#modal-create-app').modal('show'); $('#modal-create-app').modal('show');
$('#form-create-app').find('input[name="imageId"]').val(this._id); $('#form-create-app').find('input[name="imageId"]').val(this._id);

View File

@ -1,12 +1,12 @@
<template name="dashboard_layout"> <template name="dashboardLayout">
<div class="dashboard"> <div class="dashboard">
{{setTitle}} {{setTitle}}
<div class="container"> <div class="container">
<div class="dashboard-row"> <div class="dashboard-row">
{{> dashboard_menu}} {{> dashboardMenu}}
<div class="dashboard-body"> <div class="dashboard-body">
<div class="mac-window-header"><a href="#">Kitematic</a></div> <div class="mac-window-header"><a href="#">Kitematic</a></div>
{{> update_notification}} {{> updateNotification}}
{{> yield}} {{> yield}}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
<template name="dashboard_settings"> <template name="dashboardSettings">
<div class="header"> <div class="header">
<h3>Settings</h3> <h3>Settings</h3>
</div> </div>

View File

@ -1,7 +1,7 @@
var remote = require('remote'); var remote = require('remote');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
Template.dashboard_settings.events({ Template.dashboardSettings.events({
'click .btn-start-boot2docker': function (e) { 'click .btn-start-boot2docker': function (e) {
var $btn = $(e.currentTarget); var $btn = $(e.currentTarget);
$btn.html('Starting Boot2Docker...'); $btn.html('Starting Boot2Docker...');
@ -53,7 +53,7 @@ Template.dashboard_settings.events({
} }
}); });
Template.dashboard_settings.helpers({ Template.dashboardSettings.helpers({
settings: function () { settings: function () {
return Settings.findOne({}); return Settings.findOne({});
}, },

View File

@ -1,4 +1,4 @@
<template name="radial_progress"> <template name="radialProgress">
<div class="radial-progress {{class}}" data-progress="{{progress}}"> <div class="radial-progress {{class}}" data-progress="{{progress}}">
<div class="circle"> <div class="circle">
<div class="mask full"> <div class="mask full">

View File

@ -1,4 +1,4 @@
<template name="radial_spinner"> <template name="radialSpinner">
<div class="radial-spinner radial-progress" data-progress="90"> <div class="radial-spinner radial-progress" data-progress="90">
<div class="circle"> <div class="circle">
<div class="mask full"> <div class="mask full">

View File

@ -1,4 +1,4 @@
<template name="update_notification"> <template name="updateNotification">
{{#if updateAvailable}} {{#if updateAvailable}}
<div class="update-alert" role="alert"><span class="update-text">A new version of Kitematic is available.</span> <a href="#" class="btn btn-action-inverse btn-inverse btn-xs btn-update">Restart to Update</a></div> <div class="update-alert" role="alert"><span class="update-text">A new version of Kitematic is available.</span> <a href="#" class="btn btn-action-inverse btn-inverse btn-xs btn-update">Restart to Update</a></div>
{{/if}} {{/if}}

View File

@ -1,12 +1,12 @@
var ipc = require('ipc'); var ipc = require('ipc');
Template.update_notification.helpers({ Template.updateNotification.helpers({
updateAvailable: function () { updateAvailable: function () {
return Session.get('updateAvailable'); return Session.get('updateAvailable');
} }
}); });
Template.update_notification.events({ Template.updateNotification.events({
'click .btn-update': function (e) { 'click .btn-update': function (e) {
ipc.send('command', 'application:quit-install'); ipc.send('command', 'application:quit-install');
} }

View File

@ -7,6 +7,7 @@ Apps.COMMON_WEB_PORTS = [
3000, 3000,
5000, 5000,
2368, 2368,
443
]; ];
Apps.allow({ Apps.allow({
@ -31,17 +32,37 @@ Apps.helpers({
}, },
ports: function () { ports: function () {
var app = this; var app = this;
if (app.docker && app.docker.NetworkSettings.Ports) { if (!app.docker || !app.docker.NetworkSettings.Ports) {
var ports = _.map(_.keys(app.docker.NetworkSettings.Ports), function (portObj) { return [];
var port = parseInt(portObj.split('/')[0], 10);
return port;
});
return ports.join(', ');
} else {
return null;
} }
}, var results = _.map(app.docker.NetworkSettings.Ports, function (value, key) {
url: function () { var portProtocolPair = key.split('/');
return 'http://localhost:80'; // CHANGE ME var res = {
'port': parseInt(portProtocolPair[0]),
'protocol': portProtocolPair[1]
};
if (value.length) {
var port = value[0].HostPort;
res['hostIp'] = Docker.hostIp;
res['hostPort'] = port;
res['web'] = Apps.COMMON_WEB_PORTS.indexOf(res.port) !== -1;
res['url'] = 'http://' + Docker.hostIp + ':' + port;
}
return res;
});
results.sort(function (a, b) {
// prefer lower ports
if (a.web && b.web) {
return b.port - a.port;
}
if (a.web) {
return -1;
} else {
return 1;
}
});
return results;
} }
}); });

View File

@ -13,12 +13,12 @@
"url": "https://raw.githubusercontent.com/kitematic/kitematic/master/LICENSE" "url": "https://raw.githubusercontent.com/kitematic/kitematic/master/LICENSE"
}], }],
"main": "index.js", "main": "index.js",
"version": "0.3.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", "chokidar": "git+https://github.com/usekite/chokidar.git",
"dockerode": "2.0.3", "dockerode": "2.0.4",
"exec": "^0.1.2", "exec": "^0.1.2",
"moment": "2.8.1", "moment": "2.8.1",
"ncp": "0.6.0", "ncp": "0.6.0",

View File

@ -1,39 +0,0 @@
#!/bin/sh
# This script must be run as root and sets up Mac OS X to route all .kite domains to the virtual box VM with the name
# 'boot2docker-vm'. It does the following:
# 1) Adds a file under /etc/resolver/kite
# 2) Sets up a LaunchAgent for adding entries to the route table to route all requests to the Docker subnet (172.17.0.0/16)
# And expects the $IFNAME variable to contain the interface on which to send traffic to the boot2docker VM.
mkdir -p /etc/resolver
echo "nameserver 172.17.42.1" > /etc/resolver/kite
DIR=$(dirname "$0")
USER=`w -h | sort -u -t' ' -k1,1 | awk '{print $1}'`
/bin/rm -rf /Library/LaunchDaemons/com.kitematic.route.plist
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.kitematic.route</string>
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>-c</string>
<string>/usr/sbin/scutil -w State:/Network/Interface/$IFNAME/IPv4 -t 0;sudo /sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY</string>
</array>
<key>KeepAlive</key>
<false/>
<key>RunAtLoad</key>
<true/>
<key>LaunchOnlyOnce</key>
<true/>
</dict>
</plist>" > /Library/LaunchAgents/com.kitematic.route.plist
# Add entries to routing table for Kitematic boot2docker-vms
/sbin/route delete -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY > /dev/null 2>&1 || true
/sbin/route -n add -net 172.17.0.0 -netmask 255.255.0.0 -gateway $GATEWAY

View File

@ -34,17 +34,14 @@ end tell
EOF EOF
else else
osascript > /dev/null <<EOF osascript > /dev/null <<EOF
tell application "Terminal" to activate
tell application "Terminal" delay 0.4
activate tell application "System Events" to keystroke "t" using command down
end tell
tell application "System Events"
tell process "Terminal" to keystroke "t" using command down
end
tell application "Terminal" tell application "Terminal"
do script "clear && $*" in window 1 do script "clear && $*" in window 1
end tell end tell
EOF EOF
fi fi

View File

@ -30,8 +30,8 @@ popd
pushd $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
@ -56,9 +56,7 @@ cp -v resources/* dist/osx/$DIST_APP/Contents/Resources/app/resources/ || :
cecho "-----> Copying icon to $DIST_APP" $blue cecho "-----> Copying icon to $DIST_APP" $blue
cp kitematic.icns dist/osx/$DIST_APP/Contents/Resources/atom.icns cp kitematic.icns dist/osx/$DIST_APP/Contents/Resources/atom.icns
chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/install
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/unison
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

View File

@ -4,11 +4,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
BASE=$DIR/.. BASE=$DIR/..
export ROOT_URL=https://localhost:3000 export ROOT_URL=https://localhost:3000
export DOCKER_HOST=http://192.168.59.103
export DOCKER_PORT=2375
export DIR=$BASE export DIR=$BASE
cd $BASE/meteor cd $BASE/meteor
exec 3< <(meteor --settings $BASE/meteor/settings_dev.json) exec 3< <(meteor --settings $BASE/meteor/settings_dev.json)
sed '/App running at/q' <&3 ; cat <&3 & sed '/App running at/q' <&3 ; cat <&3 &
NODE_ENV=development $BASE/cache/atom-shell/Atom.app/Contents/MacOS/Atom $BASE NODE_ENV=development $BASE/cache/atom-shell/Atom.app/Contents/MacOS/Atom $BASE
kill $(ps aux | grep '.*node.*kitematic' | awk '{print $2}')
kill $(ps aux | grep '.*mongod.*kitematic' | awk '{print $2}')