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);
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,8 +67,6 @@ 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) {
@ -100,7 +74,6 @@ ImageUtil.rebuildHelper = function (image, callback) {
callback(null, null);
});
});
});
};
ImageUtil.rebuild = function (imageId) {
@ -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,15 +69,19 @@ 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);
} else {
Session.set('available', true);
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();
}
});
@ -85,34 +89,21 @@ var fixBoot2DockerVM = function (callback) {
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);
}
startUpdatingBoot2DockerUtilization = function () {
updateBoot2DockerUtilization(function (err) {
Meteor.setTimeout(updateBoot2DockerUtilization, 5000);
});
}
});
}
});
}
}, 5000);
};
Meteor.setInterval(function () {
if (!Session.get('onIntro')) {
startSyncingAppState = function () {
ImageUtil.sync();
AppUtil.sync();
Meteor.setTimeout(function () {
ImageUtil.sync();
AppUtil.sync();
}
}, 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;
'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,20 +33,18 @@ 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) {
if (err) { console.error(err); }
AppUtil.run(app, function (err) {
if (err) { console.error(err); }
});
});
});
$('#modal-create-app').bind('hidden.bs.modal', function () {
$('#slug-create-app-name').html('');
resetForm($form);

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,14 +64,11 @@ 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); }
});
});
});
}
});

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;
if (!app.docker || !app.docker.NetworkSettings.Ports) {
return [];
}
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;
});
return ports.join(', ');
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 null;
}
},
url: function () {
return 'http://localhost:80'; // CHANGE ME
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

@ -35,16 +35,13 @@ 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
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}')