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,
height: 578,
resizable: false,
frame: false,
'web-preferences': {
'web-security': false
}
frame: false
};
mainWindow = new BrowserWindow(windowOptions);
mainWindow.hide();
@ -146,6 +143,10 @@ app.on('ready', function() {
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.show();
mainWindow.focus();

View File

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

View File

@ -1,16 +1,16 @@
application-configuration@1.0.3
autoupdate@1.1.2
autoupdate@1.1.3
base64@1.0.1
binary-heap@1.0.1
blaze-tools@1.0.1
blaze@2.0.2
blaze@2.0.3
boilerplate-generator@1.0.1
callback-hook@1.0.1
check@1.0.2
ctl-helper@1.0.4
ctl@1.0.2
dburles:collection-helpers@1.0.1
ddp@1.0.10
ddp@1.0.11
deps@1.0.5
ejson@1.0.4
fastclick@1.0.1
@ -18,23 +18,28 @@ follower-livedata@1.0.2
geojson-utils@1.0.1
html-tools@1.0.2
htmljs@1.0.2
http@1.0.7
http@1.0.8
id-map@1.0.1
iron:core@0.3.4
iron:dynamic-template@0.4.1
iron:layout@0.4.1
iron:router@0.9.4
iron:controller@1.0.2
iron:core@1.0.2
iron:dynamic-template@1.0.2
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
json@1.0.1
less@1.0.10
launch-screen@1.0.0
less@1.0.11
livedata@1.0.11
logging@1.0.4
meteor-platform@1.1.2
meteor@1.1.2
minifiers@1.1.1
minimongo@1.0.4
logging@1.0.5
meteor-platform@1.2.0
meteor@1.1.3
minifiers@1.1.2
minimongo@1.0.5
mobile-status-bar@1.0.1
mongo@1.0.7
mongo@1.0.8
mrt:underscore-string-latest@2.3.3
observe-sequence@1.0.3
ordered-dict@1.0.1
@ -44,17 +49,17 @@ reactive-dict@1.0.4
reactive-var@1.0.3
reload@1.1.1
retry@1.0.1
reywood:iron-router-ga@0.3.2
reywood:iron-router-ga@0.4.1
routepolicy@1.0.2
session@1.0.3
session@1.0.4
simison:bootstrap3-less@0.3.0
spacebars-compiler@1.0.3
spacebars@1.0.3
standard-app-packages@1.0.3
templating@1.0.8
templating@1.0.9
tracker@1.0.3
ui@1.0.4
underscore@1.0.1
url@1.0.1
url@1.0.2
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.run = function (app) {
AppUtil.run = function (app, callback) {
var image = Images.findOne({_id: app.imageId});
// Delete old container if one already exists
Docker.removeContainer(app.name, function (err) {
if (err) { console.error(err); }
if (err) { callback(err); }
Docker.runContainer(app, image, function (err, container) {
if (err) { throw err; }
if (err) { callback(err); }
Docker.getContainerData(container.id, function (err, data) {
if (err) { console.error(err); }
if (err) { callback(err); }
// Set a delay for app to spin up
Meteor.setTimeout(function () {
Apps.update(app._id, {$set: {
docker: data,
status: 'READY'
}});
}, 2500);
Apps.update(app._id, {$set: {
docker: data,
status: 'READY'
}});
callback();
});
});
});
@ -29,7 +28,8 @@ AppUtil.run = function (app) {
AppUtil.restartHelper = function (app) {
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); }
Docker.getContainerData(app.docker.Id, function (err, data) {
if (err) { console.error(err); }
@ -96,9 +96,6 @@ AppUtil.remove = function (appId) {
if (err) { console.error(err); }
var appPath = path.join(Util.KITE_PATH, app.name);
Util.deleteFolder(appPath);
Docker.removeBindFolder(app.name, function () {
console.log('Deleted Kite ' + app.name + ' directory.');
});
});
}
};
@ -237,7 +234,8 @@ AppUtil.sync = function () {
config: config,
path: appPath,
logs: [],
createdAt: new Date()
createdAt: new Date(),
volumesEnabled: true
};
console.log(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.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) {

View File

@ -95,7 +95,6 @@ Docker.runContainer = function (app, image, callback) {
var builtStr = key + '=' + app.config[key];
envParam.push(builtStr);
});
console.log(envParam);
Docker.client().createContainer({
Image: image.docker.Id,
Tty: false,
@ -107,9 +106,9 @@ Docker.runContainer = function (app, image, callback) {
console.log('Created container: ' + container.id);
// Bind volumes
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) {
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
@ -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 result = [];
if (obj !== null && typeof obj === 'object') {
@ -241,9 +227,3 @@ Docker.removeImage = function (imageId, callback) {
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 = {};
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 patternString = "(FROM)(.*)";
var regex = new RegExp(patternString, "g");
@ -54,23 +45,8 @@ ImageUtil.getMetaData = function (directory) {
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) {
Util.deleteFolder(image.path);
var imageMetaData = ImageUtil.getMetaData(image.originPath);
var imageMetaData = ImageUtil.getMetaData(image.path);
if (imageMetaData.logo) {
Images.update(image._id, {
$set: {
@ -91,14 +67,11 @@ ImageUtil.rebuildHelper = function (image, callback) {
}
});
image = Images.findOne(image._id);
ImageUtil.saveFolder(image.originPath, image._id, function (err) {
if (err) { console.error(err); }
ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
if (err) { callback(err, null); return; }
ImageUtil.build(image, function (err) {
if (err) { console.error(err); }
callback(null, null);
});
ImageUtil.pull(fs.readFileSync(path.join(image.path, 'Dockerfile'), 'utf8'), image._id, function (err) {
if (err) { callback(err, null); return; }
ImageUtil.build(image, function (err) {
if (err) { console.error(err); }
callback(null, null);
});
});
};
@ -146,7 +119,6 @@ ImageUtil.pull = function (dockerfile, imageId, callback) {
console.log('From image: ' + fromImage);
var installedImage = null;
Docker.getImageData(imageId, function (err, data) {
if (err) { console.error(err); }
installedImage = data;
if (fromImage && !installedImage) {
Images.update(imageId, {
@ -191,7 +163,7 @@ ImageUtil.pull = function (dockerfile, imageId, 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); }
Images.update(image._id, {
$set: {
@ -199,7 +171,7 @@ ImageUtil.build = function (image, callback) {
}
});
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...');
response.setEncoding('utf8');
response.on('data', function (data) {
@ -265,11 +237,6 @@ ImageUtil.remove = function (imageId) {
if (err) { console.error(err); }
});
}
try {
Util.deleteFolder(image.path);
} catch (e) {
console.error(e);
}
};
ImageUtil.sync = function () {
@ -342,7 +309,6 @@ ImageUtil.sync = function () {
version: version
}
};
console.log(imageObj);
Images.insert(imageObj);
}
});

View File

@ -6,25 +6,26 @@ Router.configure({
var currentPath = Router.current().path;
ga('send', 'pageview', currentPath);
}
this.next();
}
});
DashboardController = RouteController.extend({
layoutTemplate: 'dashboard_layout',
layoutTemplate: 'dashboardLayout',
waitOn: function () {
return [Meteor.subscribe('apps'), Meteor.subscribe('images'), Meteor.subscribe('settings')];
}
});
AppController = DashboardController.extend({
layoutTemplate: 'dashboard_apps_layout',
layoutTemplate: 'dashboardAppsLayout',
data: function () {
return Apps.findOne({name: this.params.name});
}
});
ImageController = DashboardController.extend({
layoutTemplate: 'dashboard_images_layout',
layoutTemplate: 'dashboardImagesLayout',
data: function () {
return Images.findOne({_id: this.params.id});
}
@ -50,6 +51,8 @@ Router.map(function () {
Settings.insert({tracking: true});
}
Session.set('onIntro', false);
startUpdatingBoot2DockerUtilization();
// startSyncingAppState();
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) {
Boot2Docker.state(function (err, state) {
@ -118,9 +116,8 @@ Setup.steps = [
}
});
},
message: 'Starting the Docker VM...',
message: 'Starting the Docker VM...'
},
{
run: function (callback) {
Boot2Docker.ip(function (err, ip) {

View File

@ -17,4 +17,11 @@ Meteor.startup(function () {
if (!fs.existsSync(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) {
return fs.existsSync(path.join(directory, 'Dockerfile'));
};

View File

@ -69,50 +69,41 @@ Handlebars.registerHelper('displayTags', function (tags, delimiter) {
}
});
var fixBoot2DockerVM = function (callback) {
Boot2Docker.check(function (err) {
if (err) {
Session.set('available', false);
Boot2Docker.resolve(function (err) {
if (err) {
callback(err);
updateBoot2DockerUtilization = function (callback) {
Boot2Docker.exists(function (err, exists) {
if (err) { callback(err); return; }
if (exists) {
Boot2Docker.state(function (err, state) {
if (err) { callback(err); return; }
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 {
Session.set('available', true);
callback();
}
});
} else {
callback();
}
});
};
Meteor.setInterval(function () {
if (!Session.get('onIntro')) {
Boot2Docker.exists(function (err, exists) {
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);
startUpdatingBoot2DockerUtilization = function () {
updateBoot2DockerUtilization(function (err) {
Meteor.setTimeout(updateBoot2DockerUtilization, 5000);
});
};
Meteor.setInterval(function () {
if (!Session.get('onIntro')) {
startSyncingAppState = function () {
ImageUtil.sync();
AppUtil.sync();
Meteor.setTimeout(function () {
ImageUtil.sync();
AppUtil.sync();
}
}, 5000);
}, 5000);
};

View File

@ -20,6 +20,49 @@
.btn-icon {
font-size: 20px;
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 {
@ -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 {
font-size: 12px;
}

View File

@ -1,4 +1,4 @@
<template name="dashboard_apps_logs">
<template name="dashboardAppsLogs">
<div class="logs">
{{#each logs}}
<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="left-section">
<h4>Container Details</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>
<h4>Environment Variables</h4>
<p class="help-block">You can update your container's environment variables here.</p>
</div>
<div class="right-section">
@ -69,6 +35,19 @@
</form>
</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="left-section">
<h4>Delete Container</h4>

View File

@ -13,7 +13,7 @@ var getConfigVars = function ($form) {
return configVars;
};
Template.dashboard_apps_settings.events({
Template.dashboardAppsSettings.events({
'click .btn-delete-var': function (e) {
var $button = $(e.currentTarget);
$button.attr("disabled", "disabled");
@ -41,7 +41,8 @@ Template.dashboard_apps_settings.events({
e.stopPropagation();
return false;
},
'click .btn-delete-app': function () {
'click .btn-delete-app': function (e) {
e.preventDefault();
var appId = this._id;
dialog.showMessageBox({
message: 'Are you sure you want to delete this app?',
@ -52,5 +53,29 @@ Template.dashboard_apps_settings.events({
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">
<h3>Containers</h3>
<div class="options">
@ -14,7 +14,7 @@
{{#if hasItem apps}}
<div class="apps line-item-collection">
{{#each apps}}
{{> dashboard_single_app}}
{{> dashboardSingleApp}}
{{/each}}
</div>
{{else}}
@ -30,6 +30,6 @@
{{/if}}
</div>
</div>
{{> modal_create_app}}
{{> modal_create_image}}
{{> modalCreateApp}}
{{> modalCreateImage}}
</template>

View File

@ -1,4 +1,4 @@
Template.dashboard_apps.helpers({
Template.dashboardApps.helpers({
apps: function () {
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">
<h5>
{{#if $eq status 'READY'}}
@ -23,19 +23,42 @@
</h5>
<div class="options">
{{#if $eq status 'READY'}}
{{#if url}}
<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>
{{#if viewPort}}
<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}}
<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}}
<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'}}
<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 $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}}
<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}}/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>

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 () {
$('.btn-icon').tooltip();
}, 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) {
try {
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"';
Util.openTerminal(cmd);
},
'click .btn-start': function () {
'click .btn-start': function (e) {
e.preventDefault();
AppUtil.start(this._id);
$('.btn-icon').tooltip('hide');
},
'click .btn-stop': function () {
'click .btn-stop': function (e) {
e.preventDefault();
AppUtil.stop(this._id);
$('.btn-icon').tooltip('hide');
},
'click .btn-restart': function () {
'click .btn-restart': function (e) {
e.preventDefault();
AppUtil.restart(this._id);
},
'click .btn-folder': function () {
var exec = require('child_process').exec;
exec('open ' + this.path, function (err) {
if (err) { throw err; }
'click .btn-folder': function (e) {
e.preventDefault();
var appId = this._id;
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);
}
});

View File

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

View File

@ -1,6 +1,6 @@
var remote = require('remote');
Template.dashboard_menu.events({
Template.dashboardMenu.events({
'click .mac-close': function () {
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('onfocus', 'this.blur()');
$('.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>
{{> update_notification}}
{{> updateNotification}}
</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-dialog modal-small">
<div class="modal-content">

View File

@ -1,13 +1,13 @@
var fs = require('fs');
var path = require('path');
Template.modal_create_app.helpers({
Template.modalCreateApp.helpers({
images: function () {
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) {
var $form = $(e.currentTarget);
var formData = $form.serializeObject();
@ -33,18 +33,16 @@ Template.modal_create_app.events({
config: {},
path: appPath,
logs: [],
createdAt: new Date()
createdAt: new Date(),
volumesEnabled: true
};
var appId = Apps.insert(appObj);
var app = Apps.findOne(appId);
var image = Images.findOne(app.imageId);
Util.copyVolumes(image.path, app.name, function (err) {
if (err) { console.error(err); }
Docker.removeBindFolder(app.name, function (err) {
AppUtil.run(app, function (err) {
if (err) { console.error(err); }
AppUtil.run(app, function (err) {
if (err) { console.error(err); }
});
});
});
$('#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-dialog modal-small">
<div class="modal-content">

View File

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

View File

@ -1,14 +1,14 @@
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' : '';
});
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' : '';
});
Handlebars.registerHelper('currentDashboardPage', function () {
var currentPage = Router.current(true).path.split('/')[1];
var currentPage = Router.current(true).url.split('/')[1];
return currentPage;
});

View File

@ -1,4 +1,4 @@
<template name="dashboard_images_logs">
<template name="dashboardImagesLogs">
<div class="download-status">
<div class="progress">
<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="left-section">
<h4>Image Details</h4>

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<template name="dashboard_single_image">
<template name="dashboardSingleImage">
<div class="image">
<h5>
{{#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>
{{/if}}
{{#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>
{{/if}}
{{/if}}

View File

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

View File

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

View File

@ -1,12 +1,12 @@
<template name="dashboard_apps_layout">
<template name="dashboardAppsLayout">
{{setTitle this.name}}
<input id="appId" type="hidden" value="{{this._id}}">
<div class="dashboard">
<div class="container">
<div class="dashboard-row">
{{> dashboard_menu}}
{{> dashboardMenu}}
<div class="dashboard-body">
{{> menu_header}}
{{> menuHeader}}
<div class="header">
<h3>
<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 () {
$('.header .icons a').tooltip();
}, 1000);
};
Template.dashboard_apps_layout.events({
Template.dashboardAppsLayout.events({
'click .btn-view': function (e) {
try {
var open = require('open');

View File

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

View File

@ -1,10 +1,10 @@
Template.dashboard_images_layout.rendered = function () {
Template.dashboardImagesLayout.rendered = function () {
Meteor.setInterval(function () {
$('.header .icons a').tooltip();
}, 1000);
};
Template.dashboard_images_layout.events({
Template.dashboardImagesLayout.events({
'click .btn-create-app': function () {
$('#modal-create-app').modal('show');
$('#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">
{{setTitle}}
<div class="container">
<div class="dashboard-row">
{{> dashboard_menu}}
{{> dashboardMenu}}
<div class="dashboard-body">
<div class="mac-window-header"><a href="#">Kitematic</a></div>
{{> update_notification}}
{{> updateNotification}}
{{> yield}}
</div>
</div>

View File

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

View File

@ -1,7 +1,7 @@
var remote = require('remote');
var dialog = remote.require('dialog');
Template.dashboard_settings.events({
Template.dashboardSettings.events({
'click .btn-start-boot2docker': function (e) {
var $btn = $(e.currentTarget);
$btn.html('Starting Boot2Docker...');
@ -53,7 +53,7 @@ Template.dashboard_settings.events({
}
});
Template.dashboard_settings.helpers({
Template.dashboardSettings.helpers({
settings: function () {
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="circle">
<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="circle">
<div class="mask full">

View File

@ -1,4 +1,4 @@
<template name="update_notification">
<template name="updateNotification">
{{#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>
{{/if}}

View File

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

View File

@ -7,6 +7,7 @@ Apps.COMMON_WEB_PORTS = [
3000,
5000,
2368,
443
];
Apps.allow({
@ -31,17 +32,37 @@ Apps.helpers({
},
ports: function () {
var app = this;
if (app.docker && app.docker.NetworkSettings.Ports) {
var ports = _.map(_.keys(app.docker.NetworkSettings.Ports), function (portObj) {
var port = parseInt(portObj.split('/')[0], 10);
return port;
});
return ports.join(', ');
} else {
return null;
if (!app.docker || !app.docker.NetworkSettings.Ports) {
return [];
}
},
url: function () {
return 'http://localhost:80'; // CHANGE ME
var results = _.map(app.docker.NetworkSettings.Ports, function (value, key) {
var portProtocolPair = key.split('/');
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"
}],
"main": "index.js",
"version": "0.3.0",
"version": "0.4.0",
"dependencies": {
"ansi-to-html": "0.2.0",
"async": "^0.9.0",
"chokidar": "git+https://github.com/usekite/chokidar.git",
"dockerode": "2.0.3",
"dockerode": "2.0.4",
"exec": "^0.1.2",
"moment": "2.8.1",
"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
else
osascript > /dev/null <<EOF
tell application "Terminal"
activate
end tell
tell application "System Events"
tell process "Terminal" to keystroke "t" using command down
end
osascript > /dev/null <<EOF
tell application "Terminal" to activate
delay 0.4
tell application "System Events" to keystroke "t" using command down
tell application "Terminal"
do script "clear && $*" in window 1
end tell
EOF
fi

View File

@ -30,8 +30,8 @@ popd
pushd $BASE
rm -rf dist/osx
mkdir -p dist/osx/
rm -rf ./dist/osx
mkdir -p ./dist/osx/
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
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/unison
chmod +x dist/osx/$DIST_APP/Contents/Resources/app/resources/node
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/..
export ROOT_URL=https://localhost:3000
export DOCKER_HOST=http://192.168.59.103
export DOCKER_PORT=2375
export DIR=$BASE
cd $BASE/meteor
exec 3< <(meteor --settings $BASE/meteor/settings_dev.json)
sed '/App running at/q' <&3 ; cat <&3 &
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}')