1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

Rdash theme integration (#1)

* Adding latest build to dist.

* Adding latest build to dist.

* Bump other app version.

* Build latest changes.

* Bump version to 0.7.0.

* Version bump to 0.9.0-beta and remote API 1.20.

* Whoah there, back down to 0.8.0-beta.

* Merge branch 'crosbymichael-master' into crosbymichael-dist

* Add volume options in volume creation form

* display swarm cluster information in Swarm tab

* update LICENSE

* update repository URL in status bar

* remove console logs

* do not display Swarm containers anywhere in the UI

* update position for add/remove option on Volumes page

* compliant with swarm == 1.2.0 API support

* update nginx-basic-auth examples with latest nginx and swarm example

* Updated .gitignore

* update .gitignore

* reverted entry for dist/uifordocker in .gitignore

* WIP

* fix linter issues

* added logo

* update repository URL

* update .gitignore (ignore dist/*)

* add lodash

* add containers actions binding (start, stop...)

* replace image icon

* bind remove image action

* bind network remove action

* bind volume remove action

* update logo

* wip on container details

* update logo scaling, favicon and page title

* wip container view

* add containers actions in container view

* add image view

* add network view

* remove useless data in tables

* add pull image, create network modals

* add create volume modal

* update style for createVolume options

* add start container modal

* create volume modal now use a select to display drivers

* add container stats

* add containerTop view in stats view

* fix trimcontainername filter

* add container logs view

* updated .gitignore

* remove useless files/modules

* remove useless chart in image view

* replace $location usage with $state.go

* remove useless swarm example
This commit is contained in:
Anthony Lapenna 2016-06-02 17:34:03 +12:00
parent 1b206f223f
commit 0f51cb66e0
71 changed files with 2790 additions and 3211 deletions

View file

@ -1,17 +0,0 @@
<div id="build-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Build Image</h3>
</div>
<div class="modal-body">
<div id="editor"></div>
<p>{{ messages }}</p>
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="build()">Build</a>
</div>
</div>
</div>
</div>

View file

@ -1,5 +0,0 @@
angular.module('builder', [])
.controller('BuilderController', ['$scope',
function ($scope) {
$scope.template = 'app/components/builder/builder.html';
}]);

View file

@ -1,298 +1,186 @@
<div class="detail">
<div ng-if="!container.edit">
<h4>Container: {{ container.Name }}
<button class="btn btn-primary"
ng-click="container.edit = true;">Rename
</button>
</h4>
</div>
<div ng-if="container.edit">
<h4>
Container:
<input type="text" ng-model="container.newContainerName">
<button class="btn btn-success"
ng-click="renameContainer()">Save
</button>
<button class="btn btn-danger"
ng-click="container.edit = false;">&times;</button>
</h4>
</div>
<div class="btn-group detail">
<button class="btn btn-success"
ng-click="start()"
ng-show="!container.State.Running">Start
</button>
<button class="btn btn-warning"
ng-click="stop()"
ng-show="container.State.Running && !container.State.Paused">Stop
</button>
<button class="btn btn-danger"
ng-click="kill()"
ng-show="container.State.Running && !container.State.Paused">Kill
</button>
<button class="btn btn-info"
ng-click="pause()"
ng-show="container.State.Running && !container.State.Paused">Pause
</button>
<button class="btn btn-success"
ng-click="unpause()"
ng-show="container.State.Running && container.State.Paused">Unpause
</button>
<button class="btn btn-success"
ng-click="restart()"
ng-show="container.State.Running && !container.State.Stopped">Restart
</button>
<button class="btn btn-primary"
ng-click="commit()">Commit
</button>
</div>
<table class="table table-striped">
<tbody>
<tr>
<td>Created:</td>
<td>{{ container.Created | date: 'medium' }}</td>
</tr>
<tr>
<td>Path:</td>
<td>{{ container.Path }}</td>
</tr>
<tr>
<td>Args:</td>
<td>
<pre>{{ container.Args.join(' ') || 'None' }}</pre>
</td>
</tr>
<tr>
<td>Exposed Ports:</td>
<td>
<ul>
<li ng-repeat="(k, v) in container.Config.ExposedPorts">{{ k }}</li>
</ul>
</td>
</tr>
<tr>
<td>Environment:</td>
<td>
<div ng-show="!editEnv">
<button class="btn btn-default btn-xs pull-right" ng-click="editEnv = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="k in container.Config.Env">{{ k }}</li>
</ul>
</div>
<div class="form-group" ng-show="editEnv">
<label>Env:</label>
<div ng-repeat="envar in newCfg.Env">
<div class="form-group form-inline">
<div class="form-group">
<label class="sr-only">Variable Name:</label>
<input type="text" ng-model="envar.name" class="form-control input-sm"
placeholder="NAME"/>
</div>
<div class="form-group">
<label class="sr-only">Variable Value:</label>
<input type="text" ng-model="envar.value" class="form-control input-sm" style="width: 400px"
placeholder="value"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(newCfg.Env, envar)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(newCfg.Env, {name: '', value: ''})"><i class="glyphicon glyphicon-plus"></i> Add
</button>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
<tr>
<td>Labels:</td>
<td>
<table role="table" class="table">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr ng-repeat="(k, v) in container.Config.Labels">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Publish All:</td>
<td>{{ container.HostConfig.PublishAllPorts }}</td>
</tr>
<tr>
<td>Ports:</td>
<td>
<div ng-show="!editPorts">
<button class="btn btn-default btn-xs pull-right" ng-click="editPorts = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="(containerport, hostports) in container.NetworkSettings.Ports">
{{ containerport }} =>
<span class="label label-default" style="margin-right: 5px;" ng-repeat="(k,v) in hostports">{{ v.HostIp }}:{{ v.HostPort }}</span>
</li>
</ul>
</div>
<div ng-show="editPorts">
<div ng-repeat="(containerport, hostports) in newCfg.Ports" style="margin-bottom: 5px;">
<label>{{ containerport }}</label>
<div style="margin-left: 20px;">
<div ng-repeat="(k,v) in hostports" class="form-group form-inline">
<div class="form-group">
<input type="text" ng-model="v.HostIp" class="form-control input-sm" placeholder="IP address, ex. 0.0.0.0" />
</div>
<div class="form-group">
<input type="text" ng-model="v.HostPort" class="form-control input-sm"
placeholder="Port" />
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(hostports, v)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(hostports, {HostIp: '0.0.0.0', HostPort: ''})"><i class="glyphicon glyphicon-plus"></i> Add
</button>
</div>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
<tr>
<td>Hostname:</td>
<td>{{ container.Config.Hostname }}</td>
</tr>
<tr>
<td>IPAddress:</td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Cmd:</td>
<td>{{ container.Config.Cmd }}</td>
</tr>
<tr>
<td>Entrypoint:</td>
<td>
<pre>{{ container.Config.Entrypoint.join(' ') }}</pre>
</td>
</tr>
<tr>
<td>Bindings:</td>
<td>
<div ng-show="!editBinds">
<button class="btn btn-default btn-xs pull-right" ng-click="editBinds = true"><i class="glyphicon glyphicon-pencil"></i></button>
<ul>
<li ng-repeat="b in container.HostConfig.Binds">{{ b }}</li>
</ul>
</div>
<div ng-show="editBinds">
<div ng-repeat="(vol, b) in newCfg.Binds" class="form-group form-inline">
<div class="form-group">
<input type="text" ng-model="b.HostPath" class="form-control input-sm"
placeholder="Host path or volume name" style="width: 250px;" />
</div>
<div class="form-group">
<input type="text" ng-model="b.ContPath" ng-readonly="b.DefaultBind" class="form-control input-sm" placeholder="Container path" />
</div>
<div class="form-group">
<label><input type="checkbox" ng-model="b.ReadOnly" /> read only</label>
</div>
<div class="form-group">
<button class="btn btn-danger btn-sm input-sm form-control"
ng-click="rmEntry(newCfg.Binds, b)"><i class="glyphicon glyphicon-remove"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(newCfg.Binds, { ContPath: '', HostPath: '', ReadOnly: false, DefaultBind: false })"><i class="glyphicon glyphicon-plus"></i> Add
</button>
<button class="btn btn-primary btn-sm"
ng-click="restartEnv()"
ng-show="!container.State.Restarting">Commit and restart</button>
</div>
</td>
</tr>
<tr>
<td>Volumes:</td>
<td>{{ container.Volumes }}</td>
</tr>
<tr>
<td>SysInitpath:</td>
<td>{{ container.SysInitPath }}</td>
</tr>
<tr>
<td>Image:</td>
<td><a href="#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
</tr>
<tr>
<td>State:</td>
<td>
<accordion close-others="true">
<accordion-group heading="{{ container.State|getstatetext }}">
<ul>
<li ng-repeat="(key, val) in container.State">{{key}} : {{ val }}</li>
</ul>
</accordion-group>
</accordion>
</td>
</tr>
<tr>
<td>Logs:</td>
<td><a href="#/containers/{{ container.Id }}/logs">stdout/stderr</a></td>
</tr>
<tr>
<td>Stats:</td>
<td><a href="#/containers/{{ container.Id }}/stats">stats</a></td>
</tr>
<tr>
<td>Top:</td>
<td><a href="#/containers/{{ container.Id }}/top">Top</a></td>
</tr>
</tbody>
</table>
<div class="row-fluid">
<div class="span1">
Changes:
<div class="row">
<div class="col-lg-6 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-tasks"></i>
</div>
<div class="span5">
<i class="icon-refresh" style="width:32px;height:32px;" ng-click="getChanges()"></i>
<div ng-if="!container.edit">
<div class="title">{{ container.Name|trimcontainername }}</div>
<div class="comment">
Name <a href="" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
</div>
</div>
</div>
<div class="well well-large">
<ul>
<li ng-repeat="change in changes | filter:hasContent">
<strong>{{ change.Path }}</strong> {{ change.Kind }}
</li>
</ul>
</div>
<hr/>
<div class="btn-remove">
<button class="btn btn-large btn-block btn-primary btn-danger" ng-click="remove()">Remove Container</button>
</div>
<div ng-if="container.edit">
<div class="title"><input type="text" class="containerNameInput" ng-model="container.newContainerName"></div>
<div class="comment">
Name
<a href="" ng-click="container.edit = false;"><i class="fa fa-times"></i></a>
<a href="" ng-click="renameContainer()"><i class="fa fa-check-square-o"></i></a>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div ng-class="{true: 'widget-icon green pull-left', false: 'widget-icon red pull-left'}[container.State.Running]">
<i class="fa fa-heartbeat"></i>
</div>
<div class="title">{{ container.State|getstatetext }}</div>
<div class="comment">State</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-cogs"></i>
</div>
<div class="title">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-primary" ng-click="commit()">Commit</button>
<button class="btn btn-primary" ng-click="start()" ng-disabled="container.State.Running">Start</button>
<button class="btn btn-primary" ng-click="stop()" ng-disabled="!container.State.Running">Stop</button>
<button class="btn btn-primary" ng-click="kill()" ng-disabled="!container.State.Running">Kill</button>
<button class="btn btn-primary" ng-click="restart()">Restart</button>
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running && !container.State.Paused">Pause</button>
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused">Unpause</button>
<button class="btn btn-danger" ng-click="remove()" ng-disabled="container.State.Running">Remove</button>
</div>
<div class="btn-group" role="group" aria-label="...">
<a class="btn btn-default" type="button" href="#/containers/{{ container.Id }}/stats">Stats</a>
<a class="btn btn-default" type="button" href="#/containers/{{ container.Id }}/logs">Logs</a>
</div>
</div>
<div class="comment">
Actions
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-9">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Container status"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Created</td>
<td>{{ container.Created | date: 'medium' }}</td>
</tr>
<tr>
<td>Path</td>
<td>{{ container.Path }}</td>
</tr>
<tr>
<td>Args</td>
<td>{{ container.Args.join(' ') || 'None' }}</td>
</tr>
<tr>
<td>Exposed Ports</td>
<td>
<ul>
<li ng-repeat="(k, v) in container.Config.ExposedPorts">{{ k }}</li>
</ul>
</td>
</tr>
<tr>
<td>Environment</td>
<td>
<ul>
<li ng-repeat="k in container.Config.Env">{{ k }}</li>
</ul>
</td>
</tr>
<tr>
<td>Labels</td>
<td>
<table role="table" class="table">
<tr ng-repeat="(k, v) in container.Config.Labels">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Publish all ports</td>
<td>{{ container.HostConfig.PublishAllPorts }}</td>
</tr>
<tr>
<td>Ports</td>
<td>
<ul>
<li ng-repeat="(containerport, hostports) in container.NetworkSettings.Ports">
{{ containerport }} =>
<span class="label label-default" style="margin-right: 5px;" ng-repeat="(k,v) in hostports">{{ v.HostIp }}:{{ v.HostPort }}</span>
</li>
</ul>
</td>
</tr>
<tr>
<td>Hostname</td>
<td>{{ container.Config.Hostname }}</td>
</tr>
<tr>
<td>IPAddress</td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Cmd</td>
<td>{{ container.Config.Cmd }}</td>
</tr>
<tr>
<td>Entrypoint</td>
<td>{{ container.Config.Entrypoint.join(' ') }}</td>
</tr>
<tr>
<td>Bindings</td>
<td>
<ul>
<li ng-repeat="b in container.HostConfig.Binds">{{ b }}</li>
</ul>
</td>
</tr>
<tr>
<td>Volumes</td>
<td>{{ container.Volumes }}</td>
</tr>
<tr>
<td>SysInitpath</td>
<td>{{ container.SysInitPath }}</td>
</tr>
<tr>
<td>Image</td>
<td><a href="#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Container state details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr ng-repeat="(key, val) in container.State">
<td>{{key}}</td>
<td>{{ val }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
<div>

View file

@ -1,312 +1,208 @@
angular.module('container', [])
.controller('ContainerController', ['$scope', '$routeParams', '$location', 'Container', 'ContainerCommit', 'Image', 'Messages', 'ViewSpinner', '$timeout',
function ($scope, $routeParams, $location, Container, ContainerCommit, Image, Messages, ViewSpinner, $timeout) {
$scope.changes = [];
$scope.editEnv = false;
$scope.editPorts = false;
$scope.editBinds = false;
$scope.newCfg = {
Env: [],
Ports: {}
};
angular.module('container', [])
.controller('ContainerController', ['$scope', '$stateParams', '$state', '$filter', 'Container', 'ContainerCommit', 'Image', 'Messages', 'ViewSpinner', '$timeout',
function ($scope, $stateParams, $state, $filter, Container, ContainerCommit, Image, Messages, ViewSpinner, $timeout) {
$scope.changes = [];
$scope.editEnv = false;
$scope.editPorts = false;
$scope.editBinds = false;
$scope.newCfg = {
Env: [],
Ports: {}
};
var update = function () {
ViewSpinner.spin();
Container.get({id: $routeParams.id}, function (d) {
$scope.container = d;
$scope.container.edit = false;
$scope.container.newContainerName = d.Name;
var update = function () {
ViewSpinner.spin();
Container.get({id: $stateParams.id}, function (d) {
$scope.container = d;
$scope.container.edit = false;
$scope.container.newContainerName = $filter('trimcontainername')(d.Name);
// fill up env
if (d.Config.Env) {
$scope.newCfg.Env = d.Config.Env.map(function (entry) {
return {name: entry.split('=')[0], value: entry.split('=')[1]};
});
}
// fill up env
if (d.Config.Env) {
$scope.newCfg.Env = d.Config.Env.map(function (entry) {
return {name: entry.split('=')[0], value: entry.split('=')[1]};
});
}
// fill up ports
$scope.newCfg.Ports = {};
angular.forEach(d.Config.ExposedPorts, function(i, port) {
if (d.HostConfig.PortBindings && port in d.HostConfig.PortBindings) {
$scope.newCfg.Ports[port] = d.HostConfig.PortBindings[port];
}
else {
$scope.newCfg.Ports[port] = [];
}
});
// fill up ports
$scope.newCfg.Ports = {};
angular.forEach(d.Config.ExposedPorts, function(i, port) {
if (d.HostConfig.PortBindings && port in d.HostConfig.PortBindings) {
$scope.newCfg.Ports[port] = d.HostConfig.PortBindings[port];
}
else {
$scope.newCfg.Ports[port] = [];
}
});
// fill up bindings
$scope.newCfg.Binds = [];
var defaultBinds = {};
angular.forEach(d.Config.Volumes, function(value, vol) {
defaultBinds[vol] = { ContPath: vol, HostPath: '', ReadOnly: false, DefaultBind: true };
});
angular.forEach(d.HostConfig.Binds, function(binding, i) {
var mountpoint = binding.split(':')[0];
var vol = binding.split(':')[1] || '';
var ro = binding.split(':').length > 2 && binding.split(':')[2] === 'ro';
var defaultBind = false;
if (vol === '') {
vol = mountpoint;
mountpoint = '';
}
// fill up bindings
$scope.newCfg.Binds = [];
var defaultBinds = {};
angular.forEach(d.Config.Volumes, function(value, vol) {
defaultBinds[vol] = { ContPath: vol, HostPath: '', ReadOnly: false, DefaultBind: true };
});
angular.forEach(d.HostConfig.Binds, function(binding, i) {
var mountpoint = binding.split(':')[0];
var vol = binding.split(':')[1] || '';
var ro = binding.split(':').length > 2 && binding.split(':')[2] === 'ro';
var defaultBind = false;
if (vol === '') {
vol = mountpoint;
mountpoint = '';
}
if (vol in defaultBinds) {
delete defaultBinds[vol];
defaultBind = true;
}
$scope.newCfg.Binds.push({ ContPath: vol, HostPath: mountpoint, ReadOnly: ro, DefaultBind: defaultBind });
});
angular.forEach(defaultBinds, function(bind) {
$scope.newCfg.Binds.push(bind);
});
if (vol in defaultBinds) {
delete defaultBinds[vol];
defaultBind = true;
}
$scope.newCfg.Binds.push({ ContPath: vol, HostPath: mountpoint, ReadOnly: ro, DefaultBind: defaultBind });
});
angular.forEach(defaultBinds, function(bind) {
$scope.newCfg.Binds.push(bind);
});
ViewSpinner.stop();
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
ViewSpinner.stop();
});
ViewSpinner.stop();
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
ViewSpinner.stop();
});
};
};
$scope.start = function () {
ViewSpinner.spin();
Container.start({
id: $scope.container.Id,
HostConfig: $scope.container.HostConfig
}, function (d) {
update();
Messages.send("Container started", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
};
$scope.start = function () {
ViewSpinner.spin();
Container.start({
id: $scope.container.Id,
HostConfig: $scope.container.HostConfig
}, function (d) {
update();
Messages.send("Container started", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
};
$scope.stop = function () {
ViewSpinner.spin();
Container.stop({id: $routeParams.id}, function (d) {
update();
Messages.send("Container stopped", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to stop." + e.data);
});
};
$scope.stop = function () {
ViewSpinner.spin();
Container.stop({id: $stateParams.id}, function (d) {
update();
Messages.send("Container stopped", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to stop." + e.data);
});
};
$scope.kill = function () {
ViewSpinner.spin();
Container.kill({id: $routeParams.id}, function (d) {
update();
Messages.send("Container killed", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to die." + e.data);
});
};
$scope.kill = function () {
ViewSpinner.spin();
Container.kill({id: $stateParams.id}, function (d) {
update();
Messages.send("Container killed", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to die." + e.data);
});
};
$scope.restartEnv = function () {
var config = angular.copy($scope.container.Config);
$scope.commit = function () {
ViewSpinner.spin();
ContainerCommit.commit({id: $stateParams.id, repo: $scope.container.Config.Image}, function (d) {
update();
Messages.send("Container commited", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to commit." + e.data);
});
};
$scope.pause = function () {
ViewSpinner.spin();
Container.pause({id: $stateParams.id}, function (d) {
update();
Messages.send("Container paused", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to pause." + e.data);
});
};
config.Env = $scope.newCfg.Env.map(function(entry) {
return entry.name+"="+entry.value;
});
$scope.unpause = function () {
ViewSpinner.spin();
Container.unpause({id: $stateParams.id}, function (d) {
update();
Messages.send("Container unpaused", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to unpause." + e.data);
});
};
var portBindings = angular.copy($scope.newCfg.Ports);
angular.forEach(portBindings, function(item, key) {
if (item.length === 0) {
delete portBindings[key];
}
});
$scope.remove = function () {
ViewSpinner.spin();
Container.remove({id: $stateParams.id}, function (d) {
update();
$state.go('containers', {}, {reload: true});
Messages.send("Container removed", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to remove." + e.data);
});
};
$scope.restart = function () {
ViewSpinner.spin();
Container.restart({id: $stateParams.id}, function (d) {
update();
Messages.send("Container restarted", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to restart." + e.data);
});
};
var binds = [];
angular.forEach($scope.newCfg.Binds, function(b) {
if (b.ContPath !== '') {
var bindLine = '';
if (b.HostPath !== '') {
bindLine = b.HostPath + ':';
}
bindLine += b.ContPath;
if (b.ReadOnly) {
bindLine += ':ro';
}
if (b.HostPath !== '' || !b.DefaultBind) {
binds.push(bindLine);
}
}
});
$scope.hasContent = function (data) {
return data !== null && data !== undefined;
};
$scope.getChanges = function () {
ViewSpinner.spin();
Container.changes({id: $stateParams.id}, function (d) {
$scope.changes = d;
ViewSpinner.stop();
});
};
ViewSpinner.spin();
ContainerCommit.commit({id: $routeParams.id, tag: $scope.container.Config.Image, config: config }, function (d) {
if ('Id' in d) {
var imageId = d.Id;
Image.inspect({id: imageId}, function(imageData) {
// Append current host config to image with new port bindings
imageData.Config.HostConfig = angular.copy($scope.container.HostConfig);
imageData.Config.HostConfig.PortBindings = portBindings;
imageData.Config.HostConfig.Binds = binds;
if (imageData.Config.HostConfig.NetworkMode === 'host') {
imageData.Config.Hostname = '';
}
$scope.renameContainer = function () {
// #FIXME fix me later to handle http status to show the correct error message
Container.rename({id: $stateParams.id, 'name': $scope.container.newContainerName}, function (data) {
if (data.name) {
$scope.container.Name = data.name;
Messages.send("Container renamed", $stateParams.id);
} else {
$scope.container.newContainerName = $scope.container.Name;
Messages.error("Failure", "Container failed to rename.");
}
});
$scope.container.edit = false;
};
Container.create(imageData.Config, function(containerData) {
if (!('Id' in containerData)) {
Messages.error("Failure", "Container failed to create.");
return;
}
// Stop current if running
if ($scope.container.State.Running) {
Container.stop({id: $routeParams.id}, function (d) {
Messages.send("Container stopped", $routeParams.id);
// start new
Container.start({
id: containerData.Id
}, function (d) {
$location.url('/containers/' + containerData.Id + '/');
Messages.send("Container started", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
}, function (e) {
update();
Messages.error("Failure", "Container failed to stop." + e.data);
});
} else {
// start new
Container.start({
id: containerData.Id
}, function (d) {
$location.url('/containers/'+containerData.Id+'/');
Messages.send("Container started", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
});
}
$scope.addEntry = function (array, entry) {
array.push(entry);
};
$scope.rmEntry = function (array, entry) {
var idx = array.indexOf(entry);
array.splice(idx, 1);
};
}, function(e) {
update();
Messages.error("Failure", "Image failed to get." + e.data);
});
}, function (e) {
update();
Messages.error("Failure", "Image failed to get." + e.data);
});
} else {
update();
Messages.error("Failure", "Container commit failed.");
}
}, function (e) {
update();
Messages.error("Failure", "Container failed to commit." + e.data);
});
};
$scope.commit = function () {
ViewSpinner.spin();
ContainerCommit.commit({id: $routeParams.id, repo: $scope.container.Config.Image}, function (d) {
update();
Messages.send("Container commited", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to commit." + e.data);
});
};
$scope.pause = function () {
ViewSpinner.spin();
Container.pause({id: $routeParams.id}, function (d) {
update();
Messages.send("Container paused", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to pause." + e.data);
});
};
$scope.unpause = function () {
ViewSpinner.spin();
Container.unpause({id: $routeParams.id}, function (d) {
update();
Messages.send("Container unpaused", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to unpause." + e.data);
});
};
$scope.remove = function () {
ViewSpinner.spin();
Container.remove({id: $routeParams.id}, function (d) {
update();
$location.path('/containers');
Messages.send("Container removed", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to remove." + e.data);
});
};
$scope.restart = function () {
ViewSpinner.spin();
Container.restart({id: $routeParams.id}, function (d) {
update();
Messages.send("Container restarted", $routeParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to restart." + e.data);
});
};
$scope.hasContent = function (data) {
return data !== null && data !== undefined;
};
$scope.getChanges = function () {
ViewSpinner.spin();
Container.changes({id: $routeParams.id}, function (d) {
$scope.changes = d;
ViewSpinner.stop();
});
};
$scope.renameContainer = function () {
// #FIXME fix me later to handle http status to show the correct error message
Container.rename({id: $routeParams.id, 'name': $scope.container.newContainerName}, function (data) {
if (data.name) {
$scope.container.Name = data.name;
Messages.send("Container renamed", $routeParams.id);
} else {
$scope.container.newContainerName = $scope.container.Name;
Messages.error("Failure", "Container failed to rename.");
}
});
$scope.container.edit = false;
};
$scope.addEntry = function (array, entry) {
array.push(entry);
};
$scope.rmEntry = function (array, entry) {
var idx = array.indexOf(entry);
array.splice(idx, 1);
};
$scope.toggleEdit = function() {
$scope.edit = !$scope.edit;
};
update();
$scope.getChanges();
}]);
$scope.toggleEdit = function() {
$scope.edit = !$scope.edit;
};
update();
$scope.getChanges();
}]);

View file

@ -1,76 +1,79 @@
angular.module('containerLogs', [])
.controller('ContainerLogsController', ['$scope', '$routeParams', '$location', '$anchorScroll', 'ContainerLogs', 'Container', 'ViewSpinner',
function ($scope, $routeParams, $location, $anchorScroll, ContainerLogs, Container, ViewSpinner) {
$scope.stdout = '';
$scope.stderr = '';
$scope.showTimestamps = false;
$scope.tailLines = 2000;
.controller('ContainerLogsController', ['$scope', '$stateParams', '$anchorScroll', 'ContainerLogs', 'Container', 'ViewSpinner',
function ($scope, $stateParams, $anchorScroll, ContainerLogs, Container, ViewSpinner) {
$scope.state = {};
$scope.state.displayTimestampsOut = false;
$scope.state.displayTimestampsErr = false;
$scope.stdout = '';
$scope.stderr = '';
$scope.tailLines = 2000;
ViewSpinner.spin();
Container.get({id: $routeParams.id}, function (d) {
$scope.container = d;
ViewSpinner.stop();
}, function (e) {
if (e.status === 404) {
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
ViewSpinner.stop();
});
ViewSpinner.spin();
Container.get({id: $stateParams.id}, function (d) {
$scope.container = d;
ViewSpinner.stop();
}, function (e) {
if (e.status === 404) {
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
ViewSpinner.stop();
});
function getLogs() {
ViewSpinner.spin();
ContainerLogs.get($routeParams.id, {
stdout: 1,
stderr: 0,
timestamps: $scope.showTimestamps,
tail: $scope.tailLines
}, function (data, status, headers, config) {
// Replace carriage returns with newlines to clean up output
data = data.replace(/[\r]/g, '\n');
// Strip 8 byte header from each line of output
data = data.substring(8);
data = data.replace(/\n(.{8})/g, '\n');
$scope.stdout = data;
ViewSpinner.stop();
});
function getLogs() {
ViewSpinner.spin();
getLogsStdout();
getLogsStderr();
ViewSpinner.stop();
}
ContainerLogs.get($routeParams.id, {
stdout: 0,
stderr: 1,
timestamps: $scope.showTimestamps,
tail: $scope.tailLines
}, function (data, status, headers, config) {
// Replace carriage returns with newlines to clean up output
data = data.replace(/[\r]/g, '\n');
// Strip 8 byte header from each line of output
data = data.substring(8);
data = data.replace(/\n(.{8})/g, '\n');
$scope.stderr = data;
ViewSpinner.stop();
});
}
function getLogsStderr() {
ContainerLogs.get($stateParams.id, {
stdout: 0,
stderr: 1,
timestamps: $scope.state.displayTimestampsErr,
tail: $scope.tailLines
}, function (data, status, headers, config) {
// Replace carriage returns with newlines to clean up output
data = data.replace(/[\r]/g, '\n');
// Strip 8 byte header from each line of output
data = data.substring(8);
data = data.replace(/\n(.{8})/g, '\n');
$scope.stderr = data;
});
}
// initial call
getLogs();
var logIntervalId = window.setInterval(getLogs, 5000);
function getLogsStdout() {
ContainerLogs.get($stateParams.id, {
stdout: 1,
stderr: 0,
timestamps: $scope.state.displayTimestampsOut,
tail: $scope.tailLines
}, function (data, status, headers, config) {
// Replace carriage returns with newlines to clean up output
data = data.replace(/[\r]/g, '\n');
// Strip 8 byte header from each line of output
data = data.substring(8);
data = data.replace(/\n(.{8})/g, '\n');
$scope.stdout = data;
});
}
$scope.$on("$destroy", function () {
// clearing interval when view changes
clearInterval(logIntervalId);
});
// initial call
getLogs();
var logIntervalId = window.setInterval(getLogs, 5000);
$scope.scrollTo = function (id) {
$location.hash(id);
$anchorScroll();
};
$scope.$on("$destroy", function () {
// clearing interval when view changes
clearInterval(logIntervalId);
});
$scope.toggleTimestamps = function () {
getLogs();
};
$scope.toggleTimestampsOut = function () {
getLogsStdout();
};
$scope.toggleTail = function () {
getLogs();
};
}]);
$scope.toggleTimestampsErr = function () {
getLogsStderr();
};
}]);

View file

@ -1,43 +1,47 @@
<div class="row logs">
<div class="col-xs-12">
<h4>Logs for container: <a href="#/containers/{{ container.Id }}/">{{ container.Name }}</a></td></h4>
<div class="btn-group detail">
<button class="btn btn-info" ng-click="scrollTo('stdout')">stdout</button>
<button class="btn btn-warning" ng-click="scrollTo('stderr')">stderr</button>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-tasks"></i>
</div>
<div class="pull-right col-xs-6">
<div class="col-xs-6">
<a class="btn btn-primary" ng-click="toggleTail()" role="button">Reload logs</a>
<input id="tailLines" type="number" ng-style="{width: '45px'}"
ng-model="tailLines" ng-keypress="($event.which === 13)? toggleTail() : 0"/>
<label for="tailLines">lines</label>
</div>
<div class="col-xs-4">
<input id="timestampToggle" type="checkbox" ng-model="showTimestamps"
ng-change="toggleTimestamps()"/> <label for="timestampToggle">Timestamps</label>
</div>
</div>
</div>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 id="stdout" class="panel-title">STDOUT</h3>
</div>
<div class="panel-body">
<pre id="stdoutLog" class="pre-scrollable pre-x-scrollable">{{stdout}}</pre>
</div>
</div>
</div>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 id="stderr" class="panel-title">STDERR</h3>
</div>
<div class="panel-body">
<pre id="stderrLog" class="pre-scrollable pre-x-scrollable">{{stderr}}</pre>
</div>
</div>
</div>
<div class="title">{{ container.Name|trimcontainername }}</div>
<div class="comment">Name</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-info-circle" title="Stdout logs"></rd-widget-header>
<rd-widget-taskbar>
<input type="checkbox" ng-model="state.displayTimestampsOut" id="displayAllTsOut" ng-change="toggleTimestampsOut()"/>
<label for="displayAllTsOut">Display timestamps</label>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="panel-body">
<pre id="stdoutLog" class="pre-scrollable pre-x-scrollable">{{stdout}}</pre>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-exclamation-triangle" title="Stderr logs"></rd-widget-header>
<rd-widget-taskbar>
<input type="checkbox" ng-model="state.displayTimestampsErr" id="displayAllTsErr" ng-change="toggleTimestampsErr()"/>
<label for="displayAllTsErr">Display timestamps</label>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="panel-body">
<pre id="stderrLog" class="pre-scrollable pre-x-scrollable">{{stderr}}</pre>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,29 +0,0 @@
<div class="containerTop">
<div class="row">
<div class="col-xs-12">
<h1>Top for: {{ containerName }}</h1>
</div>
</div>
<div class="row">
<div class="form-group col-xs-2">
<input type="text" class="form-control" placeholder="[options] (aux)" ng-model="ps_args">
</div>
<button type="button" class="btn btn-default" ng-click="getTop()">Submit</button>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="title in containerTop.Titles">{{title}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="processInfos in containerTop.Processes">
<td ng-repeat="processInfo in processInfos track by $index">{{processInfo}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View file

@ -1,25 +0,0 @@
angular.module('containerTop', [])
.controller('ContainerTopController', ['$scope', '$routeParams', 'ContainerTop', 'Container', 'ViewSpinner', function ($scope, $routeParams, ContainerTop, Container, ViewSpinner) {
$scope.ps_args = '';
/**
* Get container processes
*/
$scope.getTop = function () {
ViewSpinner.spin();
ContainerTop.get($routeParams.id, {
ps_args: $scope.ps_args
}, function (data) {
$scope.containerTop = data;
ViewSpinner.stop();
});
};
Container.get({id: $routeParams.id}, function (d) {
$scope.containerName = d.Name.substring(1);
}, function (e) {
Messages.error("Failure", e.data);
});
$scope.getTop();
}]);

View file

@ -1,76 +1,82 @@
<div ng-include="template" ng-controller="StartContainerController"></div>
<h2>Containers:</h2>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">Actions <b class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="startAction()">Start</a></li>
<li><a tabindex="-1" href="" ng-click="stopAction()">Stop</a></li>
<li><a tabindex="-1" href="" ng-click="restartAction()">Restart</a></li>
<li><a tabindex="-1" href="" ng-click="killAction()">Kill</a></li>
<li><a tabindex="-1" href="" ng-click="pauseAction()">Pause</a></li>
<li><a tabindex="-1" href="" ng-click="unpauseAction()">Unpause</a></li>
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
</ul>
<div class="pull-right form-inline">
<input type="checkbox" ng-model="displayAll" id="displayAll" ng-change="toggleGetAll()"/> <label for="displayAll">Display All</label>&nbsp;
<input type="text" class="form-control" style="vertical-align: center" id="filter" placeholder="Filter" ng-model="filter"/> <label class="sr-only" for="filter">Filter</label>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Containers">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-primary" ng-click="startAction()" ng-disabled="!state.selectedItemCount">Start</button>
<button type="button" class="btn btn-primary" ng-click="stopAction()" ng-disabled="!state.selectedItemCount">Stop</button>
<button type="button" class="btn btn-primary" ng-click="killAction()" ng-disabled="!state.selectedItemCount">Kill</button>
<button type="button" class="btn btn-primary" ng-click="restartAction()" ng-disabled="!state.selectedItemCount">Restart</button>
<button type="button" class="btn btn-primary" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount">Pause</button>
<button type="button" class="btn btn-primary" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount">Unpause</button>
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#create-modal">Start a new container...</button>
</div>
</div>
<div class="pull-right">
<input type="checkbox" ng-model="state.displayAll" id="displayAll" ng-change="toggleGetAll()"/><label for="displayAll">Display All</label>
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/containers/" ng-click="order('Names')">
Name
<span ng-show="sortType == 'Names' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Name
<span ng-show="sortType == 'Names' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/containers/" ng-click="order('Image')">
Image
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Image
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/containers/" ng-click="order('Command')">
Command
<span ng-show="sortType == 'Command' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Command
<span ng-show="sortType == 'Command' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/containers/" ng-click="order('Created')">
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/containers/" ng-click="order('Status')">
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="container in (filteredContainers = ( containers | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="container.Checked" /></td>
<td><a href="#/containers/{{ container.Id }}/">{{ container|containername}}</a></td>
<td><a href="#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
<td>{{ container.Command|truncate:40 }}</td>
<td>{{ container.Created|getdate }}</td>
<td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td>
</tr>
</tbody>
</table>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
<td><a href="#/containers/{{ container.Id }}">{{ container|containername}}</a></td>
<td><a href="#/images/{{ container.Image }}/">{{ container.Image }}</a></td>
<td>{{ container.Command|truncate:40 }}</td>
<td>{{ container.Created|getdate }}</td>
<td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td>
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>

View file

@ -1,118 +1,138 @@
angular.module('containers', [])
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner',
function ($scope, Container, Settings, Messages, ViewSpinner) {
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.displayAll = Settings.displayAll;
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner',
function ($scope, Container, Settings, Messages, ViewSpinner) {
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.state = {};
$scope.state.displayAll = Settings.displayAll;
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
var update = function (data) {
ViewSpinner.spin();
Container.query(data, function (d) {
$scope.containers = d.map(function (item) {
return new ContainerViewModel(item);
});
ViewSpinner.stop();
});
};
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
var batch = function (items, action, msg) {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
update({all: Settings.displayAll ? 1 : 0});
}
};
angular.forEach(items, function (c) {
if (c.Checked) {
if (action === Container.start) {
Container.get({id: c.Id}, function (d) {
c = d;
counter = counter + 1;
action({id: c.Id, HostConfig: c.HostConfig || {}}, function (d) {
Messages.send("Container " + msg, c.Id);
var index = $scope.containers.indexOf(c);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
complete();
});
}
else {
counter = counter + 1;
action({id: c.Id}, function (d) {
Messages.send("Container " + msg, c.Id);
var index = $scope.containers.indexOf(c);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
var update = function (data) {
ViewSpinner.spin();
$scope.state.selectedItemCount = 0;
Container.query(data, function (d) {
$scope.containers = d.filter(function (container) {
return container.Image !== 'swarm';
}).map(function (container) {
return new ContainerViewModel(container);
});
ViewSpinner.stop();
});
};
}
var batch = function (items, action, msg) {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
update({all: Settings.displayAll ? 1 : 0});
}
};
angular.forEach(items, function (c) {
if (c.Checked) {
if (action === Container.start) {
Container.get({id: c.Id}, function (d) {
c = d;
counter = counter + 1;
action({id: c.Id, HostConfig: c.HostConfig || {}}, function (d) {
Messages.send("Container " + msg, c.Id);
var index = $scope.containers.indexOf(c);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
Messages.error("Not found", "Container not found.");
} else {
Messages.error("Failure", e.data);
}
complete();
});
}
else {
counter = counter + 1;
action({id: c.Id}, function (d) {
Messages.send("Container " + msg, c.Id);
var index = $scope.containers.indexOf(c);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
if (counter === 0) {
ViewSpinner.stop();
}
};
}
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredContainers, function (i) {
i.Checked = $scope.toggle;
});
};
}
});
if (counter === 0) {
ViewSpinner.stop();
}
};
$scope.toggleGetAll = function () {
Settings.displayAll = $scope.displayAll;
update({all: Settings.displayAll ? 1 : 0});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.startAction = function () {
batch($scope.containers, Container.start, "Started");
};
$scope.toggleSelectAll = function () {
$scope.state.selectedItem = $scope.state.toggle;
angular.forEach($scope.state.filteredContainers, function (i) {
i.Checked = $scope.state.toggle;
});
if ($scope.state.toggle) {
$scope.state.selectedItemCount = $scope.state.filteredContainers.length;
} else {
$scope.state.selectedItemCount = 0;
}
};
$scope.stopAction = function () {
batch($scope.containers, Container.stop, "Stopped");
};
$scope.toggleGetAll = function () {
Settings.displayAll = $scope.state.displayAll;
update({all: Settings.displayAll ? 1 : 0});
};
$scope.restartAction = function () {
batch($scope.containers, Container.restart, "Restarted");
};
$scope.startAction = function () {
batch($scope.containers, Container.start, "Started");
};
$scope.killAction = function () {
batch($scope.containers, Container.kill, "Killed");
};
$scope.stopAction = function () {
batch($scope.containers, Container.stop, "Stopped");
};
$scope.pauseAction = function () {
batch($scope.containers, Container.pause, "Paused");
};
$scope.restartAction = function () {
batch($scope.containers, Container.restart, "Restarted");
};
$scope.unpauseAction = function () {
batch($scope.containers, Container.unpause, "Unpaused");
};
$scope.killAction = function () {
batch($scope.containers, Container.kill, "Killed");
};
$scope.removeAction = function () {
batch($scope.containers, Container.remove, "Removed");
};
$scope.pauseAction = function () {
batch($scope.containers, Container.pause, "Paused");
};
update({all: Settings.displayAll ? 1 : 0});
}]);
$scope.unpauseAction = function () {
batch($scope.containers, Container.unpause, "Unpaused");
};
$scope.removeAction = function () {
batch($scope.containers, Container.remove, "Removed");
};
update({all: Settings.displayAll ? 1 : 0});
}]);

View file

@ -1,25 +0,0 @@
<div class="detail">
<h2>Containers Network</h2>
<div class="row">
<div class="input-group">
<input type="text" ng-model="query" autofocus="true" class="form-control"
placeholder="Search" ng-change="network.selectContainers(query)"/>
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
</div>
</div>
<div class="row">
<div class="btn-group">
<button class="btn btn-warning" ng-click="network.hideSelected()">Hide Selected</button>
<button class="btn btn-info" ng-click="network.showSelectedDownstream()">Show Selected Downstream</button>
<button class="btn btn-info" ng-click="network.showSelectedUpstream()">Show Selected Upstream</button>
<button class="btn btn-success" ng-click="network.showAll()">Show All</button>
</div>
<input type="checkbox" ng-model="includeStopped" id="includeStopped" ng-change="toggleIncludeStopped()"/> <label
for="includeStopped">Include stopped containers</label>
</div>
<div class="row">
<vis-network data="network.data" options="network.options" events="network.events"
component="network.component"/>
</div>
</div>

View file

@ -1,271 +0,0 @@
angular.module('containersNetwork', ['ngVis'])
.controller('ContainersNetworkController', ['$scope', '$location', 'Container', 'Messages', 'VisDataSet', function ($scope, $location, Container, Messages, VisDataSet) {
function ContainerNode(data) {
this.Id = data.Id;
// names have the following format: /Name
this.Name = data.Name.substring(1);
this.Image = data.Config.Image;
this.Running = data.State.Running;
var dataLinks = data.HostConfig.Links;
if (dataLinks != null) {
this.Links = {};
for (var i = 0; i < dataLinks.length; i++) {
// links have the following format: /TargetContainerName:/SourceContainerName/LinkAlias
var link = dataLinks[i].split(":");
var target = link[0].substring(1);
var alias = link[1].substring(link[1].lastIndexOf("/") + 1);
// only keep shortest alias
if (this.Links[target] == null || alias.length < this.Links[target].length) {
this.Links[target] = alias;
}
}
}
var dataVolumes = data.HostConfig.VolumesFrom;
//converting array into properties for simpler and faster access
if (dataVolumes != null) {
this.VolumesFrom = {};
for (var j = 0; j < dataVolumes.length; j++) {
this.VolumesFrom[dataVolumes[j]] = true;
}
}
}
function ContainersNetworkData() {
this.nodes = new VisDataSet();
this.edges = new VisDataSet();
this.addContainerNode = function (container) {
this.nodes.add({
id: container.Id,
label: container.Name,
title: "<ul style=\"list-style-type:none; padding: 0px; margin: 0px\">" +
"<li><strong>ID:</strong> " + container.Id + "</li>" +
"<li><strong>Image:</strong> " + container.Image + "</li>" +
"</ul>",
color: (container.Running ? "#8888ff" : "#cccccc")
});
};
this.hasEdge = function (from, to) {
return this.edges.getIds({
filter: function (item) {
return item.from === from.Id && item.to === to.Id;
}
}).length > 0;
};
this.addLinkEdgeIfExists = function (from, to) {
if (from.Links != null && from.Links[to.Name] != null && !this.hasEdge(from, to)) {
this.edges.add({
from: from.Id,
to: to.Id,
label: from.Links[to.Name]
});
}
};
this.addVolumeEdgeIfExists = function (from, to) {
if (from.VolumesFrom != null && (from.VolumesFrom[to.Id] != null || from.VolumesFrom[to.Name] != null) && !this.hasEdge(from, to)) {
this.edges.add({
from: from.Id,
to: to.Id,
color: {color: '#A0A0A0', highlight: '#A0A0A0', hover: '#848484'}
});
}
};
this.removeContainersNodes = function (containersIds) {
this.nodes.remove(containersIds);
};
}
function ContainersNetwork() {
this.data = new ContainersNetworkData();
this.containers = {};
this.selectedContainersIds = [];
this.shownContainersIds = [];
this.events = {
select: function (event) {
$scope.network.selectedContainersIds = event.nodes;
$scope.$apply(function () {
$scope.query = '';
});
},
doubleClick: function (event) {
$scope.$apply(function () {
$location.path('/containers/' + event.nodes[0]);
});
}
};
this.options = {
navigation: true,
keyboard: true,
height: '500px', width: '700px',
nodes: {
shape: 'box'
},
edges: {
style: 'arrow'
},
physics: {
barnesHut: {
springLength: 200
}
}
};
this.addContainer = function (data) {
var container = new ContainerNode(data);
this.containers[container.Id] = container;
this.shownContainersIds.push(container.Id);
this.data.addContainerNode(container);
for (var otherContainerId in this.containers) {
var otherContainer = this.containers[otherContainerId];
this.data.addLinkEdgeIfExists(container, otherContainer);
this.data.addLinkEdgeIfExists(otherContainer, container);
this.data.addVolumeEdgeIfExists(container, otherContainer);
this.data.addVolumeEdgeIfExists(otherContainer, container);
}
};
this.selectContainers = function (query) {
if (this.component != null) {
this.selectedContainersIds = this.searchContainers(query);
this.component.selectNodes(this.selectedContainersIds);
}
};
this.searchContainers = function (query) {
if (query.trim() === "") {
return [];
}
var selectedContainersIds = [];
for (var i = 0; i < this.shownContainersIds.length; i++) {
var container = this.containers[this.shownContainersIds[i]];
if (container.Name.indexOf(query) > -1 ||
container.Image.indexOf(query) > -1 ||
container.Id.indexOf(query) > -1) {
selectedContainersIds.push(container.Id);
}
}
return selectedContainersIds;
};
this.hideSelected = function () {
var i = 0;
while (i < this.shownContainersIds.length) {
if (this.selectedContainersIds.indexOf(this.shownContainersIds[i]) > -1) {
this.shownContainersIds.splice(i, 1);
} else {
i++;
}
}
this.data.removeContainersNodes(this.selectedContainersIds);
$scope.query = '';
this.selectedContainersIds = [];
};
this.searchDownstream = function (containerId, downstreamContainersIds) {
if (downstreamContainersIds.indexOf(containerId) > -1) {
return;
}
downstreamContainersIds.push(containerId);
var container = this.containers[containerId];
if (container.Links == null && container.VolumesFrom == null) {
return;
}
for (var otherContainerId in this.containers) {
var otherContainer = this.containers[otherContainerId];
if (container.Links != null && container.Links[otherContainer.Name] != null) {
this.searchDownstream(otherContainer.Id, downstreamContainersIds);
} else if (container.VolumesFrom != null &&
container.VolumesFrom[otherContainer.Id] != null) {
this.searchDownstream(otherContainer.Id, downstreamContainersIds);
}
}
};
this.updateShownContainers = function (newShownContainersIds) {
for (var containerId in this.containers) {
if (newShownContainersIds.indexOf(containerId) > -1 &&
this.shownContainersIds.indexOf(containerId) === -1) {
this.data.addContainerNode(this.containers[containerId]);
} else if (newShownContainersIds.indexOf(containerId) === -1 &&
this.shownContainersIds.indexOf(containerId) > -1) {
this.data.removeContainersNodes(containerId);
}
}
this.shownContainersIds = newShownContainersIds;
};
this.showSelectedDownstream = function () {
var downstreamContainersIds = [];
for (var i = 0; i < this.selectedContainersIds.length; i++) {
this.searchDownstream(this.selectedContainersIds[i], downstreamContainersIds);
}
this.updateShownContainers(downstreamContainersIds);
};
this.searchUpstream = function (containerId, upstreamContainersIds) {
if (upstreamContainersIds.indexOf(containerId) > -1) {
return;
}
upstreamContainersIds.push(containerId);
var container = this.containers[containerId];
for (var otherContainerId in this.containers) {
var otherContainer = this.containers[otherContainerId];
if (otherContainer.Links != null && otherContainer.Links[container.Name] != null) {
this.searchUpstream(otherContainer.Id, upstreamContainersIds);
} else if (otherContainer.VolumesFrom != null &&
otherContainer.VolumesFrom[container.Id] != null) {
this.searchUpstream(otherContainer.Id, upstreamContainersIds);
}
}
};
this.showSelectedUpstream = function () {
var upstreamContainersIds = [];
for (var i = 0; i < this.selectedContainersIds.length; i++) {
this.searchUpstream(this.selectedContainersIds[i], upstreamContainersIds);
}
this.updateShownContainers(upstreamContainersIds);
};
this.showAll = function () {
for (var containerId in this.containers) {
if (this.shownContainersIds.indexOf(containerId) === -1) {
this.data.addContainerNode(this.containers[containerId]);
this.shownContainersIds.push(containerId);
}
}
};
}
$scope.network = new ContainersNetwork();
var showFailure = function (event) {
Messages.error('Failure', e.data);
};
var addContainer = function (container) {
$scope.network.addContainer(container);
};
var update = function (data) {
Container.query(data, function (d) {
for (var i = 0; i < d.length; i++) {
Container.get({id: d[i].Id}, addContainer, showFailure);
}
});
};
update({all: 0});
$scope.includeStopped = false;
$scope.toggleIncludeStopped = function () {
$scope.network.updateShownContainers([]);
update({all: $scope.includeStopped ? 1 : 0});
};
}]);

View file

@ -0,0 +1,45 @@
<div id="create-network-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times" aria-hidden="true"></i></button>
<h3>Create network</h3>
</div>
<div class="modal-body">
<form novalidate role="form" name="createNetworkForm">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='my_network'
ng-model="createNetworkConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<input type="text" placeholder='bridge'
ng-model="createNetworkConfig.Driver" class="form-control"/>
</div>
<div class="form-group">
<label>Subnet:</label>
<input type="text" placeholder='172.20.0.0/16'
ng-model="createNetworkConfig.IPAM.Config[0].Subnet" class="form-control"/>
</div>
<div class="form-group">
<label>IPRange:</label>
<input type="text" placeholder='172.20.10.0/24'
ng-model="createNetworkConfig.IPAM.Config[0].IPRange" class="form-control"/>
</div>
<div class="form-group">
<label>Gateway:</label>
<input type="text" placeholder='172.20.10.11'
ng-model="createNetworkConfig.IPAM.Config[0].Gateway" class="form-control"/>
</div>
</form>
</div>
<div class="alert alert-error" id="error-message" style="display:none">
{{ error }}
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="createNetwork(createNetworkConfig)">Create</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,41 @@
angular.module('createNetwork', [])
.controller('CreateNetworkController', ['$scope', '$state', 'Messages', 'Network', 'ViewSpinner', 'errorMsgFilter',
function ($scope, $state, Messages, Network, ViewSpinner, errorMsgFilter) {
$scope.template = 'app/components/createNetwork/createNetwork.html';
$scope.init = function () {
$scope.createNetworkConfig = {
"Name": '',
"Driver": '',
"IPAM": {
"Config": [{}]
}
};
};
$scope.init();
$scope.createNetwork = function addNetwork(createNetworkConfig) {
if (_.isEmpty(createNetworkConfig.IPAM.Config[0])) {
delete createNetworkConfig.IPAM;
}
$('#error-message').hide();
ViewSpinner.spin();
$('#create-network-modal').modal('hide');
Network.create(createNetworkConfig, function (d) {
if (d.Id) {
Messages.send("Network created", d.Id);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
$scope.init();
$state.go('networks', {}, {reload: true});
}, function (e) {
ViewSpinner.stop();
$scope.error = "Cannot pull image " + imageName + " Reason: " + e.data;
$('#create-network-modal').modal('show');
$('#error-message').show();
});
};
}]);

View file

@ -0,0 +1,40 @@
<div id="create-volume-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times" aria-hidden="true"></i></button>
<h3>Create volume</h3>
</div>
<div class="modal-body">
<form novalidate role="form" name="createVolumeForm">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='my_volume'
ng-model="createVolumeConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<ui-select ng-model="selectedDriver.value" theme="bootstrap">
<ui-select-match>
<span ng-bind="$select.selected"></span>
</ui-select-match>
<ui-select-choices repeat="driver in availableDrivers">
<span ng-bind="driver"></span>
</ui-select-choices>
</ui-select>
</div>
<div class="form-group" ng-show="selectedDriver.value == 'local-persist'">
<label>Mount point:</label>
<input type="text" ng-model="createVolumeConfig.DriverOpts.mountpoint" name="" placeholder="/volume/my_volume" class="form-control">
</div>
</form>
</div>
<div class="alert alert-error" id="error-message" style="display:none">
{{ error }}
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="addVolume(createVolumeConfig)">Create</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,39 @@
angular.module('createVolume', [])
.controller('CreateVolumeController', ['$scope', '$state', 'Messages', 'Volume', 'ViewSpinner', 'errorMsgFilter',
function ($scope, $state, Messages, Volume, ViewSpinner, errorMsgFilter) {
$scope.template = 'app/components/createVolume/createVolume.html';
$scope.init = function () {
$scope.createVolumeConfig = {
"Name": "",
"Driver": "",
"DriverOpts": {}
};
$scope.availableDrivers = ['local', 'local-persist'];
$scope.selectedDriver = { value: $scope.availableDrivers[0] };
};
$scope.init();
$scope.addVolume = function addVolume(createVolumeConfig) {
$('#error-message').hide();
ViewSpinner.spin();
$('#create-volume-modal').modal('hide');
createVolumeConfig.Driver = $scope.selectedDriver.value;
console.log(JSON.stringify(createVolumeConfig, null, 4));
Volume.create(createVolumeConfig, function (d) {
if (d.Name) {
Messages.send("Volume created", d.Name);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
$state.go('volumes', {}, {reload: true});
}, function (e) {
ViewSpinner.stop();
$scope.error = "Cannot create volume " + createVolumeConfig.Name + " Reason: " + e.data;
$('#create-volume-modal').modal('show');
$('#error-message').show();
});
};
}]);

View file

@ -1,52 +1,71 @@
<div class="col-xs-offset-1">
<!--<div class="sidebar span4">
<div ng-include="template" ng-controller="SideBarController"></div>
</div>-->
<div class="row">
<div class="col-xs-10" id="masthead" style="display:none">
<div class="jumbotron">
<h1>UI For Docker</h1>
<p class="lead">The UI for Docker container engine</p>
<a class="btn btn-large btn-success" href="http://docker.io">Learn more.</a>
</div>
<div class="row">
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon blue pull-left">
<i class="fa fa-tasks"></i>
</div>
</div>
<div class="row">
<div class="col-xs-10">
<div class="col-xs-5">
<h3>Running Containers</h3>
<ul>
<li ng-repeat="container in containers|orderBy:predicate">
<a href="#/containers/{{ container.Id }}/">{{ container|containername }}</a>
<span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span>
</li>
</ul>
</div>
<div class="col-xs-5 text-right">
<h3>Status</h3>
<canvas id="containers-chart" class="pull-right">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
<div id="chart-legend"></div>
</div>
<div class="title">{{ containerData.total }}</div>
<div class="comment">Containers</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon green pull-left">
<i class="fa fa-tasks"></i>
</div>
</div>
<div class="row">
<div class="col-xs-10" id="stats">
<h4>Containers created</h4>
<canvas id="containers-started-chart" width="700">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
<h4>Images created</h4>
<canvas id="images-created-chart" width="700">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
<div class="title">{{ containerData.running }}</div>
<div class="comment">Running</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon red pull-left">
<i class="fa fa-tasks"></i>
</div>
</div>
<div class="title">{{ containerData.stopped }}</div>
<div class="comment">Stopped</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon gray pull-left">
<i class="fa fa-tasks"></i>
</div>
<div class="title">{{ containerData.ghost }}</div>
<div class="comment">Ghost</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Containers created"></rd-widget-header>
<rd-widget-body>
<canvas id="containers-started-chart" width="770" height="230">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Images created"></rd-widget-header>
<rd-widget-body>
<canvas id="images-created-chart" width="770" height="230">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,75 +1,46 @@
angular.module('dashboard', [])
.controller('DashboardController', ['$scope', 'Container', 'Image', 'Settings', 'LineChart', function ($scope, Container, Image, Settings, LineChart) {
$scope.predicate = '-Created';
$scope.containers = [];
.controller('DashboardController', ['$scope', 'Container', 'Image', 'Settings', 'LineChart', function ($scope, Container, Image, Settings, LineChart) {
var getStarted = function (data) {
$scope.totalContainers = data.length;
LineChart.build('#containers-started-chart', data, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
var s = $scope;
Image.query({}, function (d) {
s.totalImages = d.length;
LineChart.build('#images-created-chart', d, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
});
};
$scope.containerData = {};
var opts = {animation: false};
if (Settings.firstLoad) {
opts.animation = true;
Settings.firstLoad = false;
localStorage.setItem('firstLoad', false);
$('#masthead').show();
var buildCharts = function (data) {
$scope.containerData.total = data.length;
LineChart.build('#containers-started-chart', data, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
var s = $scope;
Image.query({}, function (d) {
s.totalImages = d.length;
LineChart.build('#images-created-chart', d, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
});
};
setTimeout(function () {
$('#masthead').slideUp('slow');
}, 5000);
}
Container.query({all: 1}, function (d) {
var running = 0;
var ghost = 0;
var stopped = 0;
Container.query({all: 1}, function (d) {
var running = 0;
var ghost = 0;
var stopped = 0;
// TODO: centralize that
var containers = d.filter(function (container) {
return container.Image !== 'swarm';
});
for (var i = 0; i < d.length; i++) {
var item = d[i];
for (var i = 0; i < containers.length; i++) {
var item = containers[i];
if (item.Status === "Ghost") {
ghost += 1;
} else if (item.Status.indexOf('Exit') !== -1) {
stopped += 1;
} else {
running += 1;
}
}
$scope.containerData.running = running;
$scope.containerData.stopped = stopped;
$scope.containerData.ghost = ghost;
if (item.Status === "Ghost") {
ghost += 1;
} else if (item.Status.indexOf('Exit') !== -1) {
stopped += 1;
} else {
running += 1;
$scope.containers.push(new ContainerViewModel(item));
}
}
getStarted(d);
var c = new Chart($('#containers-chart').get(0).getContext("2d"));
var data = [
{
value: running,
color: '#5bb75b',
title: 'Running'
}, // running
{
value: stopped,
color: '#C7604C',
title: 'Stopped'
}, // stopped
{
value: ghost,
color: '#E2EAE9',
title: 'Ghost'
} // ghost
];
c.Doughnut(data, opts);
var lgd = $('#chart-legend').get(0);
legend(lgd, data);
});
}]);
buildCharts(containers);
});
}]);

View file

@ -0,0 +1,35 @@
angular.module('dashboard')
.controller('MasterCtrl', ['$scope', '$cookieStore', 'Settings', function ($scope, $cookieStore, Settings) {
/**
* Sidebar Toggle & Cookie Control
*/
var mobileView = 992;
$scope.getWidth = function() {
return window.innerWidth;
};
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
if (newValue >= mobileView) {
if (angular.isDefined($cookieStore.get('toggle'))) {
$scope.toggle = ! $cookieStore.get('toggle') ? false : true;
} else {
$scope.toggle = true;
}
} else {
$scope.toggle = false;
}
});
$scope.toggleSidebar = function() {
$scope.toggle = !$scope.toggle;
$cookieStore.put('toggle', $scope.toggle);
};
window.onresize = function() {
$scope.$apply();
};
$scope.uiVersion = Settings.uiVersion;
}]);

View file

@ -1,34 +0,0 @@
<div class="row">
<div class="col-xs-12">
<h2>Events</h2>
<form class="form-inline">
<div class="form-group">
<label for="since">Since:</label>
<input id="since" type="datetime-local" ng-model="model.since" class="form-control" step="any"/>
</div>
<div class="form-group">
<label for="until">Until:</label>
<input id="until" type="datetime-local" ng-model="model.until" class="form-control" step="any"/>
</div>
<button ng-click="updateEvents()" class="btn btn-primary">Update</button>
</form>
<br>
<table class="table">
<tbody>
<tr>
<th>Event</th>
<th>From</th>
<th>ID</th>
<th>Time</th>
</tr>
<tr ng-repeat="event in dockerEvents">
<td ng-bind="event.status"/>
<td ng-bind="event.from"/>
<td ng-bind="event.id"/>
<td ng-bind="event.time * 1000 | date:'medium'"/>
</tr>
</tbody>
</table>
</div>
</div>

View file

@ -1,42 +0,0 @@
angular.module('events', ['ngOboe'])
.controller('EventsController', ['Settings', '$scope', 'Oboe', 'Messages', '$timeout', function (Settings, $scope, oboe, Messages, $timeout) {
$scope.updateEvents = function () {
$scope.dockerEvents = [];
// TODO: Clean up URL building
var url = Settings.url + '/events?';
if ($scope.model.since) {
var sinceSecs = Math.floor($scope.model.since.getTime() / 1000);
url += 'since=' + sinceSecs + '&';
}
if ($scope.model.until) {
var untilSecs = Math.floor($scope.model.until.getTime() / 1000);
url += 'until=' + untilSecs;
}
oboe({
url: url,
pattern: '{id status time}'
})
.then(function (node) {
// finished loading
$timeout(function () {
$scope.$apply();
});
}, function (error) {
// handle errors
Messages.error("Failure", error.data);
}, function (node) {
// node received
$scope.dockerEvents.push(node);
});
};
// Init
$scope.model = {};
$scope.model.since = new Date(Date.now() - 86400000); // 24 hours in the past
$scope.model.until = new Date();
$scope.updateEvents();
}]);

View file

@ -1,9 +0,0 @@
angular.module('footer', [])
.controller('FooterController', ['$scope', 'Settings', 'Version', function ($scope, Settings, Version) {
$scope.template = 'app/components/footer/statusbar.html';
$scope.uiVersion = Settings.uiVersion;
Version.get({}, function (d) {
$scope.apiVersion = d.ApiVersion;
});
}]);

View file

@ -1,6 +0,0 @@
<footer class="center well">
<p>
<small>Docker API Version: <strong>{{ apiVersion }}</strong> UI Version: <strong>{{ uiVersion }}</strong> <a
class="pull-right" href="https://github.com/kevana/ui-for-docker">UI For Docker</a></small>
</p>
</footer>

View file

@ -1,120 +1,92 @@
<div ng-include="template" ng-controller="StartContainerController"></div>
<div class="alert alert-error" id="error-message" style="display:none">
{{ error }}
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-clone"></i>
</div>
<div class="title">{{ id }}</div>
<div class="comment">Image ID</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="detail">
<h4>Image: {{ id }}</h4>
<div class="btn-group detail">
<button class="btn btn-success" data-toggle="modal" data-target="#create-modal">Start Container</button>
</div>
<div>
<h4>Containers created:</h4>
<canvas id="containers-started-chart" width="750">
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
</canvas>
</div>
<table class="table table-striped">
<tbody>
<tr>
<td>Tags:</td>
<td>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-cogs"></i>
</div>
<div class="title">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-danger" ng-click="removeImage(id)">Remove</button>
</div>
</div>
<div class="comment">
Actions
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Image details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Created</td>
<td>{{ image.Created | date: 'medium'}}</td>
</tr>
<tr>
<td>Tags</td>
<td>
<ul>
<li ng-repeat="tag in RepoTags">{{ tag }}
<button ng-click="removeImage(tag)" class="btn btn-sm btn-danger">Remove tag</button>
</li>
<li ng-repeat="tag in RepoTags">{{ tag }}
<button ng-click="removeImage(tag)" class="btn btn-sm btn-danger">Remove tag</button>
</li>
</ul>
</td>
</tr>
<tr>
<td>Created:</td>
<td>{{ image.Created | date: 'medium'}}</td>
</tr>
<tr>
<td>Parent:</td>
<td><a href="#/images/{{ image.Parent }}/">{{ image.Parent }}</a></td>
</tr>
<tr>
<td>Size (Virtual Size):</td>
<td>{{ image.Size|humansize }} ({{ image.VirtualSize|humansize }})</td>
</tr>
</td>
</tr>
<tr>
<td>Parent</td>
<td><a href="#/images/{{ image.Parent }}/">{{ image.Parent }}</a></td>
</tr>
<tr>
<td>Size (Virtual Size)</td>
<td>{{ image.Size|humansize }} ({{ image.VirtualSize|humansize }})</td>
</tr>
<tr>
<td>Hostname:</td>
<td>{{ image.ContainerConfig.Hostname }}</td>
</tr>
<tr>
<td>User:</td>
<td>{{ image.ContainerConfig.User }}</td>
</tr>
<tr>
<td>Cmd:</td>
<td>{{ image.ContainerConfig.Cmd }}</td>
</tr>
<tr>
<td>Volumes:</td>
<td>{{ image.ContainerConfig.Volumes }}</td>
</tr>
<tr>
<td>Volumes from:</td>
<td>{{ image.ContainerConfig.VolumesFrom }}</td>
</tr>
<tr>
<td>Built with:</td>
<td>Docker {{ image.DockerVersion }} on {{ image.Os}}, {{ image.Architecture }}</td>
</tr>
</tbody>
</table>
<div class="row-fluid">
<div class="span1">
History:
</div>
<div class="span5">
<i class="icon-refresh" style="width:32px;height:32px;" ng-click="getHistory()"></i>
</div>
</div>
<div class="well well-large">
<ul>
<li ng-repeat="change in history">
<strong>{{ change.Id }}</strong>: Created: {{ change.Created|getdate }} Created by: {{ change.CreatedBy
}}
</li>
</ul>
</div>
<hr/>
<div class="row-fluid">
<form class="form-inline" role="form">
<fieldset>
<legend>Tag image</legend>
<div class="form-group">
<label>Tag:</label>
<input type="text" placeholder="repo" ng-model="tagInfo.repo" class="form-control">
<input type="text" placeholder="version" ng-model="tagInfo.version" class="form-control">
</div>
<div class="form-group">
<label class="checkbox">
<input type="checkbox" ng-model="tagInfo.force" class="form-control"/> Force?
</label>
</div>
<input type="button" ng-click="addTag()" value="Add Tag" class="btn btn-primary"/>
</fieldset>
</form>
</div>
<hr/>
<div class="btn-remove">
<button class="btn btn-large btn-block btn-primary btn-danger" ng-click="removeImage(id)">Remove Image</button>
</div>
<tr>
<td>Hostname</td>
<td>{{ image.ContainerConfig.Hostname }}</td>
</tr>
<tr>
<td>User</td>
<td>{{ image.ContainerConfig.User }}</td>
</tr>
<tr>
<td>Cmd</td>
<td>{{ image.ContainerConfig.Cmd }}</td>
</tr>
<tr>
<td>Volumes</td>
<td>{{ image.ContainerConfig.Volumes }}</td>
</tr>
<tr>
<td>Volumes from</td>
<td>{{ image.ContainerConfig.VolumesFrom }}</td>
</tr>
<tr>
<td>Built with</td>
<td>Docker {{ image.DockerVersion }} on {{ image.Os}}, {{ image.Architecture }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,106 +1,58 @@
angular.module('image', [])
.controller('ImageController', ['$scope', '$q', '$routeParams', '$location', 'Image', 'Container', 'Messages', 'LineChart',
function ($scope, $q, $routeParams, $location, Image, Container, Messages, LineChart) {
$scope.history = [];
$scope.tagInfo = {repo: '', version: '', force: false};
$scope.id = '';
$scope.repoTags = [];
.controller('ImageController', ['$scope', '$q', '$stateParams', '$state', 'Image', 'Container', 'Messages', 'LineChart',
function ($scope, $q, $stateParams, $state, Image, Container, Messages, LineChart) {
$scope.tagInfo = {repo: '', version: '', force: false};
$scope.id = '';
$scope.repoTags = [];
$scope.removeImage = function (id) {
Image.remove({id: id}, function (d) {
d.forEach(function(msg){
var key = Object.keys(msg)[0];
Messages.send(key, msg[key]);
});
// If last message key is 'Deleted' then assume the image is gone and send to images page
if (d[d.length-1].Deleted) {
$location.path('/images');
} else {
$location.path('/images/' + $scope.id); // Refresh the current page.
}
}, function (e) {
$scope.error = e.data;
$('#error-message').show();
});
};
$scope.removeImage = function (id) {
Image.remove({id: id}, function (d) {
d.forEach(function(msg){
var key = Object.keys(msg)[0];
Messages.send(key, msg[key]);
});
// If last message key is 'Deleted' then assume the image is gone and send to images page
if (d[d.length-1].Deleted) {
$state.go('images', {}, {reload: true});
} else {
$state.go('image', {id: $scope.id}, {reload: true});
}
}, function (e) {
$scope.error = e.data;
$('#error-message').show();
});
};
$scope.getHistory = function () {
Image.history({id: $routeParams.id}, function (d) {
$scope.history = d;
});
};
/**
* Get RepoTags from the /images/query endpoint instead of /image/json,
* for backwards compatibility with Docker API versions older than 1.21
* @param {string} imageId
*/
function getRepoTags(imageId) {
Image.query({}, function (d) {
d.forEach(function(image) {
if (image.Id === imageId && image.RepoTags[0] !== '<none>:<none>') {
$scope.RepoTags = image.RepoTags;
}
});
});
}
$scope.addTag = function () {
var tag = $scope.tagInfo;
Image.tag({
id: $routeParams.id,
repo: tag.repo,
tag: tag.version,
force: tag.force ? 1 : 0
}, function (d) {
Messages.send("Tag Added", $routeParams.id);
$location.path('/images/' + $scope.id);
}, function (e) {
$scope.error = e.data;
$('#error-message').show();
});
};
function getContainersFromImage($q, Container, imageId) {
var defer = $q.defer();
Container.query({all: 1, notruc: 1}, function (d) {
var containers = [];
for (var i = 0; i < d.length; i++) {
var c = d[i];
if (c.ImageID === imageId) {
containers.push(new ContainerViewModel(c));
}
}
defer.resolve(containers);
});
return defer.promise;
}
/**
* Get RepoTags from the /images/query endpoint instead of /image/json,
* for backwards compatibility with Docker API versions older than 1.21
* @param {string} imageId
*/
function getRepoTags(imageId) {
Image.query({}, function (d) {
d.forEach(function(image) {
if (image.Id === imageId && image.RepoTags[0] !== '<none>:<none>') {
$scope.RepoTags = image.RepoTags;
}
});
});
}
Image.get({id: $routeParams.id}, function (d) {
$scope.image = d;
$scope.id = d.Id;
if (d.RepoTags) {
$scope.RepoTags = d.RepoTags;
} else {
getRepoTags($scope.id);
}
getContainersFromImage($q, Container, $scope.id).then(function (containers) {
LineChart.build('#containers-started-chart', containers, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
});
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
$scope.error = "Image not found.<br />" + $routeParams.id;
} else {
$scope.error = e.data;
}
$('#error-message').show();
});
$scope.getHistory();
}]);
Image.get({id: $stateParams.id}, function (d) {
$scope.image = d;
$scope.id = d.Id;
if (d.RepoTags) {
$scope.RepoTags = d.RepoTags;
} else {
getRepoTags($scope.id);
}
}, function (e) {
if (e.status === 404) {
$('.detail').hide();
$scope.error = "Image not found.<br />" + $stateParams.id;
} else {
$scope.error = e.data;
}
$('#error-message').show();
});
}]);

View file

@ -1,64 +1,67 @@
<div ng-include="template" ng-controller="BuilderController"></div>
<div ng-include="template" ng-controller="PullImageController"></div>
<h2>Images:</h2>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">Actions <b class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
<li><a data-toggle="modal" data-target="#pull-modal" href="">Pull</a></li>
</ul>
<div class="pull-right form-inline">
<input type="text" class="form-control" id="filter" placeholder="Filter" ng-model="filter"/> <label class="sr-only" for="filter">Filter</label>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Images">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#pull-modal">Pull new image...</button>
</div>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/images/" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/images/" ng-click="order('RepoTags')">
Repository
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Repository
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/images/" ng-click="order('VirtualSize')">
VirtualSize
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
VirtualSize
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
</th>
<th>
<a href="#/images/" ng-click="order('Created')">
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="image in (filteredImages = (images | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="image.Checked" /></td>
<td><a href="#/images/{{ image.Id }}/?tag={{ image|repotag }}">{{ image.Id|truncate:20}}</a></td>
<td>{{ image|repotag }}</td>
<td>{{ image.VirtualSize|humansize }}</td>
<td>{{ image.Created|getdate }}</td>
</tr>
</tbody>
</table>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="image in (state.filteredImages = (images | filter:state.filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
<td><a href="#/images/{{ image.Id }}/?tag={{ image|repotag }}">{{ image.Id|truncate:20}}</a></td>
<td>{{ image|repotag }}</td>
<td>{{ image.VirtualSize|humansize }}</td>
<td>{{ image.Created|getdate }}</td>
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>

View file

@ -1,60 +1,75 @@
angular.module('images', [])
.controller('ImagesController', ['$scope', 'Image', 'ViewSpinner', 'Messages',
function ($scope, Image, ViewSpinner, Messages) {
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
.controller('ImagesController', ['$scope', 'Image', 'ViewSpinner', 'Messages',
function ($scope, Image, ViewSpinner, Messages) {
$scope.state = {};
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.showBuilder = function () {
$('#build-modal').modal('show');
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.state.filteredImages, function (i) {
i.Checked = $scope.state.toggle;
});
if ($scope.state.toggle) {
$scope.state.selectedItemCount = $scope.state.filteredImages.length;
} else {
$scope.state.selectedItemCount = 0;
}
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.images, function (i) {
if (i.Checked) {
counter = counter + 1;
Image.remove({id: i.Id}, function (d) {
angular.forEach(d, function (resource) {
Messages.send("Image deleted", resource.Deleted);
});
var index = $scope.images.indexOf(i);
$scope.images.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredImages, function (i) {
i.Checked = $scope.toggle;
});
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.images, function (i) {
if (i.Checked) {
counter = counter + 1;
Image.remove({id: i.Id}, function (d) {
angular.forEach(d, function (resource) {
Messages.send("Image deleted", resource.Deleted);
});
var index = $scope.images.indexOf(i);
$scope.images.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
ViewSpinner.spin();
Image.query({}, function (d) {
$scope.images = d.map(function (item) {
return new ImageViewModel(item);
});
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}]);
function fetchImages() {
ViewSpinner.spin();
Image.query({}, function (d) {
$scope.images = d.map(function (item) {
return new ImageViewModel(item);
});
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchImages();
}]);

View file

@ -1,110 +0,0 @@
<div class="detail">
<h2>Docker Information</h2>
<div>
<p class="lead">
<strong>API Endpoint: </strong>{{ endpoint }}<br/>
<strong>API Version: </strong>{{ docker.ApiVersion }}<br/>
<strong>Docker version: </strong>{{ docker.Version }}<br/>
<strong>Git Commit: </strong>{{ docker.GitCommit }}<br/>
<strong>Go Version: </strong>{{ docker.GoVersion }}<br/>
</p>
</div>
<table class="table table-striped">
<tbody>
<tr>
<td>Containers:</td>
<td>{{ info.Containers }}</td>
</tr>
<tr>
<td>Images:</td>
<td>{{ info.Images }}</td>
</tr>
<tr>
<td>Debug:</td>
<td>{{ info.Debug }}</td>
</tr>
<tr>
<td>CPUs:</td>
<td>{{ info.NCPU }}</td>
</tr>
<tr>
<td>Total Memory:</td>
<td>{{ info.MemTotal|humansize }}</td>
</tr>
<tr>
<td>Operating System:</td>
<td>{{ info.OperatingSystem }}</td>
</tr>
<tr>
<td>Kernel Version:</td>
<td>{{ info.KernelVersion }}</td>
</tr>
<tr>
<td>ID:</td>
<td>{{ info.ID }}</td>
</tr>
<tr>
<td>Labels:</td>
<td>{{ info.Labels }}</td>
</tr>
<tr>
<td>File Descriptors:</td>
<td>{{ info.NFd }}</td>
</tr>
<tr>
<td>Goroutines:</td>
<td>{{ info.NGoroutines }}</td>
</tr>
<tr>
<td>Storage Driver:</td>
<td>{{ info.Driver }}</td>
</tr>
<tr>
<td>Storage Driver Status:</td>
<td>
<p ng-repeat="val in info.DriverStatus">
{{ val[0] }}: {{ val[1] }}
</p>
</td>
</tr>
<tr>
<td>Execution Driver:</td>
<td>{{ info.ExecutionDriver }}</td>
</tr>
<tr>
<td>Events:</td>
<td><a href="#/events">Events</a></td>
</tr>
<tr>
<td>IPv4 Forwarding:</td>
<td>{{ info.IPv4Forwarding }}</td>
</tr>
<tr>
<td>Index Server Address:</td>
<td>{{ info.IndexServerAddress }}</td>
</tr>
<tr>
<td>Init Path:</td>
<td>{{ info.InitPath }}</td>
</tr>
<tr>
<td>Docker Root Directory:</td>
<td>{{ info.DockerRootDir }}</td>
</tr>
<tr>
<td>Init SHA1</td>
<td>{{ info.InitSha1 }}</td>
</tr>
<tr>
<td>Memory Limit:</td>
<td>{{ info.MemoryLimit }}</td>
</tr>
<tr>
<td>Swap Limit:</td>
<td>{{ info.SwapLimit }}</td>
</tr>
</tbody>
</table>
</div>

View file

@ -1,14 +0,0 @@
angular.module('info', [])
.controller('InfoController', ['$scope', 'Info', 'Version', 'Settings',
function ($scope, Info, Version, Settings) {
$scope.info = {};
$scope.docker = {};
$scope.endpoint = Settings.endpoint;
Version.get({}, function (d) {
$scope.docker = d;
});
Info.get({}, function (d) {
$scope.info = d;
});
}]);

View file

@ -1,21 +0,0 @@
<div class="masthead">
<h3 class="text-muted">UI For Docker</h3>
<div class="col-xs-11">
<ul class="nav well">
<li><a href="#/">Dashboard</a></li>
<li><a href="#/containers/">Containers</a></li>
<li><a href="#/containers_network/">Containers Network</a></li>
<li><a href="#/images/">Images</a></li>
<li ng-if="showNetworksVolumes"><a href="#/networks/">Networks</a></li>
<li ng-if="showNetworksVolumes"><a href="#/volumes/">Volumes</a></li>
<li><a href="#/info/">Info</a></li>
</ul>
</div>
<div class="col-xs-1">
<button class="btn btn-primary" ng-click="refresh()">
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
Refresh
</button>
</div>
</div>

View file

@ -1,15 +0,0 @@
angular.module('masthead', [])
.controller('MastheadController', ['$scope', 'Version', function ($scope, Version) {
$scope.template = 'app/components/masthead/masthead.html';
$scope.showNetworksVolumes = false;
Version.get(function(d) {
if (d.ApiVersion >= 1.21) {
$scope.showNetworksVolumes = true;
}
});
$scope.refresh = function() {
location.reload();
};
}]);

View file

@ -1,110 +1,120 @@
<div class="detail">
<h4>Network: {{ network.Name }}</h4>
<table class="table table-striped">
<tbody>
<tr>
<td>Name:</td>
<td>{{ network.Name }}</td>
</tr>
<tr>
<td>Id:</td>
<td>{{ network.Id }}</td>
</tr>
<tr>
<td>Scope:</td>
<td>{{ network.Scope }}</td>
</tr>
<tr>
<td>Driver:</td>
<td>{{ network.Driver }}</td>
</tr>
<tr>
<td>IPAM:</td>
<td>
<div class="row">
<div class="col-lg-9 col-md-9 col-xs-9">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-sitemap"></i>
</div>
<div class="title">{{ network.Name }}</div>
<div class="comment">Name</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-3 col-xs-3">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-cogs"></i>
</div>
<div class="title">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-default" disabled>Connect container...</button>
<button class="btn btn-danger" ng-click="remove(id)">Remove</button>
</div>
</div>
<div class="comment">
Actions
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-sitemap" title="Network details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Id</td>
<td>{{ network.Id }}</td>
</tr>
<tr>
<td>Scope</td>
<td>{{ network.Scope }}</td>
</tr>
<tr>
<td>Driver</td>
<td>{{ network.Driver }}</td>
</tr>
<tr>
<td>IPAM</td>
<td>
<table class="table table-striped">
<tr>
<td>Driver:</td>
<td>{{ network.IPAM.Driver }}</td>
</tr>
<tr>
<td>Subnet:</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
</tr>
<tr>
<td>Gateway:</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
<tr>
<td>Driver</td>
<td>{{ network.IPAM.Driver }}</td>
</tr>
<tr>
<td>Subnet</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
</tr>
<tr>
<td>Gateway</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Containers:</td>
<td>
</td>
</tr>
<tr>
<td>Containers</td>
<td>
<table class="table table-striped" ng-repeat="(Id, container) in network.Containers">
<tr>
<td>Id:</td>
<td><a href="#/containers/{{ Id }}">{{ Id }}</a></td>
<td>
<button ng-click="disconnect(network.Id, Id)" class="btn btn-danger btn-sm">
Disconnect from network
</button>
</td>
</tr>
<tr>
<td>EndpointID:</td>
<td>{{ container.EndpointID}}</td>
</tr>
<tr>
<td>MacAddress:</td>
<td>{{ container.MacAddress}}</td>
</tr>
<tr>
<td>IPv4Address:</td>
<td>{{ container.IPv4Address}}</td>
</tr>
<tr>
<td>IPv6Address:</td>
<td>{{ container.IPv6Address}}</td>
</tr>
<tr>
<td>Id</td>
<td><a href="#/containers/{{ Id }}">{{ Id }}</a></td>
</tr>
<tr>
<td>EndpointID</td>
<td>{{ container.EndpointID}}</td>
</tr>
<tr>
<td>MacAddress</td>
<td>{{ container.MacAddress}}</td>
</tr>
<tr>
<td>IPv4Address</td>
<td>{{ container.IPv4Address}}</td>
</tr>
<tr>
<td>IPv6Address</td>
<td>{{ container.IPv6Address}}</td>
</tr>
<tr>
<td colspan="2">
<button ng-click="disconnect(network.Id, Id)" class="btn btn-danger">
Disconnect from network
</button>
</td>
</tr>
</table>
<form class="form-inline">
<div class="form-group">
<label>Container ID:
<input ng-model="containerId" placeholder="3613f73ba0e4" class="form-control">
</label>
</div>
<button ng-click="connect(network.Id, containerId)" class="btn btn-primary">
Connect
</button>
</form>
</td>
</tr>
<tr>
<td>Options:</td>
<td>
</td>
</tr>
<tr>
<td>Options</td>
<td>
<table role="table" class="table table-striped">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr ng-repeat="(k, v) in network.Options">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
<tr ng-repeat="(k, v) in network.Options">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<hr/>
<div class="btn-remove">
<button class="btn btn-large btn-block btn-primary btn-danger" ng-click="removeImage(id)">Remove Network
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,56 +1,37 @@
angular.module('network', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/networks/:id/', {
templateUrl: 'app/components/network/network.html',
controller: 'NetworkController'
}]).controller('NetworkController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$state', '$stateParams', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $state, $stateParams, errorMsgFilter) {
$scope.disconnect = function disconnect(networkId, containerId) {
ViewSpinner.spin();
Network.disconnect({id: $stateParams.id}, {Container: containerId}, function (d) {
ViewSpinner.stop();
Messages.send("Container disconnected", containerId);
$state.go('network', {id: $stateParams.id}, {reload: true});
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
}]).controller('NetworkController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$routeParams', '$location', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $routeParams, $location, errorMsgFilter) {
};
$scope.disconnect = function disconnect(networkId, containerId) {
ViewSpinner.spin();
Network.disconnect({id: $routeParams.id}, {Container: containerId}, function (d) {
ViewSpinner.stop();
Messages.send("Container disconnected", containerId);
$location.path('/networks/' + $routeParams.id); // Refresh the current page.
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.connect = function connect(networkId, containerId) {
ViewSpinner.spin();
Network.connect({id: $routeParams.id}, {Container: containerId}, function (d) {
ViewSpinner.stop();
var errmsg = errorMsgFilter(d);
if (errmsg) {
Messages.error('Error', errmsg);
} else {
Messages.send("Container connected", d);
}
$location.path('/networks/' + $routeParams.id); // Refresh the current page.
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.remove = function remove(networkId) {
ViewSpinner.spin();
Network.remove({id: $routeParams.id}, function (d) {
ViewSpinner.stop();
Messages.send("Network removed", d);
$location.path('/networks'); // Go to the networks page
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.remove = function remove(networkId) {
ViewSpinner.spin();
Network.remove({id: $stateParams.id}, function (d) {
ViewSpinner.stop();
Messages.send("Network removed", "");
$state.go('networks', {});
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
ViewSpinner.spin();
Network.get({id: $routeParams.id}, function (d) {
$scope.network = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}]);
ViewSpinner.spin();
Network.get({id: $stateParams.id}, function (d) {
$scope.network = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}]);

View file

@ -1,121 +1,91 @@
<h2>Networks:</h2>
<div ng-include="template" ng-controller="CreateNetworkController"></div>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">Actions <b
class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
</ul>
<div class="pull-right form-inline">
<input type="text" class="form-control" id="filter" placeholder="Filter" ng-model="filter"/> <label
class="sr-only" for="filter">Filter</label>
</div>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-sitemap" title="Networks">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#create-network-modal">Create new network...</button>
</div>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/networks/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Scope')">
Scope
<span ng-show="sortType == 'Scope' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Scope' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Driver')">
IPAM Driver
<span ng-show="sortType == 'IPAM.Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Subnet')">
IPAM Subnet
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Gateway')">
IPAM Gateway
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="network.Checked" ng-change="selectItem(network)"/></td>
<td><a href="#/networks/{{ network.Id }}/">{{ network.Name|truncate:20}}</a></td>
<td>{{ network.Id }}</td>
<td>{{ network.Scope }}</td>
<td>{{ network.Driver }}</td>
<td>{{ network.IPAM.Driver }}</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/networks/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Scope')">
Scope
<span ng-show="sortType == 'Scope' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Scope' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Driver')">
IPAM Driver
<span ng-show="sortType == 'IPAM.Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Subnet')">
IPAM Subnet
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/networks/" ng-click="order('IPAM.Config[0].Gateway')">
IPAM Gateway
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="network in ( filteredNetworks = (networks | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="network.Checked"/></td>
<td><a href="#/networks/{{ network.Id }}/">{{ network.Name|truncate:20}}</a></td>
<td>{{ network.Id }}</td>
<td>{{ network.Scope }}</td>
<td>{{ network.Driver }}</td>
<td>{{ network.IPAM.Driver }}</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<form role="form" class="">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='isolated_nw'
ng-model="createNetworkConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<input type="text" placeholder='bridge'
ng-model="createNetworkConfig.Driver" class="form-control"/>
</div>
<div class="form-group">
<label>Subnet:</label>
<input type="text" placeholder='172.20.0.0/16'
ng-model="createNetworkConfig.IPAM.Config[0].Subnet" class="form-control"/>
</div>
<div class="form-group">
<label>IPRange:</label>
<input type="text" placeholder='172.20.10.0/24'
ng-model="createNetworkConfig.IPAM.Config[0].IPRange" class="form-control"/>
</div>
<div class="form-group">
<label>Gateway:</label>
<input type="text" placeholder='172.20.10.11'
ng-model="createNetworkConfig.IPAM.Config[0].Gateway" class="form-control"/>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addNetwork(createNetworkConfig)">
Create Network
</button>
</form>
</div>
</div>

View file

@ -1,87 +1,71 @@
angular.module('networks', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/networks/', {
templateUrl: 'app/components/networks/networks.html',
controller: 'NetworksController'
angular.module('networks', [])
.controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, errorMsgFilter) {
$scope.state = {};
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.state.filteredNetworks, function (i) {
i.Checked = $scope.state.toggle;
});
}]).controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $route, errorMsgFilter) {
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.createNetworkConfig = {
"Name": '',
"Driver": '',
"IPAM": {
"Config": [{
"Subnet": '',
"IPRange": '',
"Gateway": ''
}]
}
};
if ($scope.state.toggle) {
$scope.state.selectedItemCount = $scope.state.filteredNetworks.length;
} else {
$scope.state.selectedItemCount = 0;
}
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.networks, function (network) {
if (network.Checked) {
counter = counter + 1;
Network.remove({id: network.Id}, function (d) {
Messages.send("Network deleted", network.Id);
var index = $scope.networks.indexOf(network);
$scope.networks.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.networks, function (network) {
if (network.Checked) {
counter = counter + 1;
Network.remove({id: network.Id}, function (d) {
Messages.send("Network deleted", network.Id);
var index = $scope.networks.indexOf(network);
$scope.networks.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredNetworks, function (i) {
i.Checked = $scope.toggle;
});
};
$scope.addNetwork = function addNetwork(createNetworkConfig) {
ViewSpinner.spin();
Network.create(createNetworkConfig, function (d) {
if (d.Id) {
Messages.send("Network created", d.Id);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
fetchNetworks();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
};
function fetchNetworks() {
ViewSpinner.spin();
Network.query({}, function (d) {
$scope.networks = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchNetworks();
}]);
function fetchNetworks() {
ViewSpinner.spin();
Network.query({}, function (d) {
$scope.networks = d;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchNetworks();
}]);

View file

@ -1,44 +1,35 @@
<div id="pull-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Pull Image</h3>
</div>
<div class="modal-body">
<form novalidate role="form" name="pullForm">
<!--<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Image name</span>
<input type="text" class="form-control" placeholder="imageName" aria-describedby="basic-addon1">
</div>-->
<div class="form-group">
<label>Registry:</label>
<input type="text" ng-model="config.registry" class="form-control"
placeholder="Registry. Leave empty to user docker hub"/>
</div>
<div class="form-group">
<label>Repo:</label>
<input type="text" ng-model="config.repo" class="form-control"
placeholder="Repository - usually your username."/>
</div>
<div class="form-group">
<label>Image Name:</label>
<input type="text" ng-model="config.fromImage" class="form-control" placeholder="Image name"
required/>
</div>
<div class="form-group">
<label>Tag Name:</label>
<input type="text" ng-model="config.tag" class="form-control"
placeholder="Tag name. If empty it will download ALL tags."/>
</div>
</form>
</div>
<div class="alert alert-error" id="error-message" style="display:none">
{{ error }}
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="pull()">Pull</a>
</div>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times" aria-hidden="true"></i></button>
<h3>Pull image</h3>
</div>
<div class="modal-body">
<form novalidate role="form" name="pullForm">
<div class="form-group">
<label>Registry:</label>
<input type="text" ng-model="config.registry" class="form-control"
placeholder="Leave empty to user DockerHub"/>
</div>
<div class="form-group">
<label>Image Name:</label>
<input type="text" ng-model="config.fromImage" class="form-control" placeholder="username/image"
required/>
</div>
<div class="form-group">
<label>Tag Name:</label>
<input type="text" ng-model="config.tag" class="form-control"
placeholder="Leave empty to download ALL tags."/>
</div>
</form>
</div>
<div class="alert alert-error" id="error-message" style="display:none">
{{ error }}
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="pull()">Pull</a>
</div>
</div>
</div>
</div>

View file

@ -1,56 +1,56 @@
angular.module('pullImage', [])
.controller('PullImageController', ['$scope', '$log', 'Messages', 'Image', 'ViewSpinner',
function ($scope, $log, Messages, Image, ViewSpinner) {
$scope.template = 'app/components/pullImage/pullImage.html';
.controller('PullImageController', ['$scope', '$state', 'Messages', 'Image', 'ViewSpinner',
function ($scope, $state, Messages, Image, ViewSpinner) {
$scope.template = 'app/components/pullImage/pullImage.html';
$scope.init = function () {
$scope.config = {
registry: '',
repo: '',
fromImage: '',
tag: 'latest'
};
};
$scope.init = function () {
$scope.config = {
registry: '',
fromImage: '',
tag: 'latest'
};
};
$scope.init();
$scope.init();
function failedRequestHandler(e, Messages) {
Messages.error('Error', errorMsgFilter(e));
}
function failedRequestHandler(e, Messages) {
Messages.error('Error', errorMsgFilter(e));
}
$scope.pull = function () {
$('#error-message').hide();
var config = angular.copy($scope.config);
var imageName = (config.registry ? config.registry + '/' : '' ) +
(config.repo ? config.repo + '/' : '') +
(config.fromImage) +
(config.tag ? ':' + config.tag : '');
$scope.pull = function () {
$('#error-message').hide();
var config = angular.copy($scope.config);
var imageName = (config.registry ? config.registry + '/' : '' ) +
(config.fromImage) +
(config.tag ? ':' + config.tag : '');
ViewSpinner.spin();
$('#pull-modal').modal('hide');
Image.create(config, function (data) {
ViewSpinner.stop();
if (data.constructor === Array) {
var f = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
//check for error
if (f) {
var d = data[data.length - 1];
$scope.error = "Cannot pull image " + imageName + " Reason: " + d.error;
$('#pull-modal').modal('show');
$('#error-message').show();
} else {
Messages.send("Image Added", imageName);
$scope.init();
}
} else {
Messages.send("Image Added", imageName);
$scope.init();
}
}, function (e) {
ViewSpinner.stop();
$scope.error = "Cannot pull image " + imageName + " Reason: " + e.data;
$('#pull-modal').modal('show');
$('#error-message').show();
});
};
}]);
ViewSpinner.spin();
$('#pull-modal').modal('hide');
Image.create(config, function (data) {
ViewSpinner.stop();
if (data.constructor === Array) {
var f = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
//check for error
if (f) {
var d = data[data.length - 1];
$scope.error = "Cannot pull image " + imageName + " Reason: " + d.error;
$('#pull-modal').modal('show');
$('#error-message').show();
} else {
Messages.send("Image Added", imageName);
$scope.init();
$state.go('images', {}, {reload: true});
}
} else {
Messages.send("Image Added", imageName);
$scope.init();
$state.go('images', {}, {reload: true});
}
}, function (e) {
ViewSpinner.stop();
$scope.error = "Cannot pull image " + imageName + " Reason: " + e.data;
$('#pull-modal').modal('show');
$('#error-message').show();
});
};
}]);

View file

@ -1,11 +0,0 @@
<div class="well">
<strong>Running containers:</strong>
<br/>
<strong>Endpoint: </strong>{{ endpoint }}
<ul>
<li ng-repeat="container in containers">
<a href="#/containers/{{ container.Id }}/">{{ container.Id|truncate:20 }}</a>
<span class="pull-right label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span>
</li>
</ul>
</div>

View file

@ -1,11 +0,0 @@
angular.module('sidebar', [])
.controller('SideBarController', ['$scope', 'Container', 'Settings',
function ($scope, Container, Settings) {
$scope.template = 'partials/sidebar.html';
$scope.containers = [];
$scope.endpoint = Settings.endpoint;
Container.query({all: 0}, function (d) {
$scope.containers = d;
});
}]);

View file

@ -1,159 +1,163 @@
angular.module('startContainer', ['ui.bootstrap'])
.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter',
function ($scope, $routeParams, $location, Container, Messages, containernameFilter, errorMsgFilter) {
$scope.template = 'app/components/startContainer/startcontainer.html';
.controller('StartContainerController', ['$scope', '$state', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter', 'ViewSpinner',
function ($scope, $state, Container, Messages, containernameFilter, errorMsgFilter, ViewSpinner) {
$scope.template = 'app/components/startContainer/startcontainer.html';
Container.query({all: 1}, function (d) {
$scope.containerNames = d.map(function (container) {
return containernameFilter(container);
});
Container.query({all: 1}, function (d) {
$scope.containerNames = d.map(function (container) {
return containernameFilter(container);
});
});
$scope.config = {
Env: [],
Labels: [],
Volumes: [],
SecurityOpts: [],
HostConfig: {
PortBindings: [],
Binds: [],
Links: [],
Dns: [],
DnsSearch: [],
VolumesFrom: [],
CapAdd: [],
CapDrop: [],
Devices: [],
LxcConf: [],
ExtraHosts: []
}
};
$scope.menuStatus = {
containerOpen: true,
hostConfigOpen: false
};
function failedRequestHandler(e, Messages) {
Messages.error('Error', errorMsgFilter(e));
}
function rmEmptyKeys(col) {
for (var key in col) {
if (col[key] === null || col[key] === undefined || col[key] === '' || ($.isPlainObject(col[key]) && $.isEmptyObject(col[key])) || col[key].length === 0) {
delete col[key];
}
}
}
function getNames(arr) {
return arr.map(function (item) {
return item.name;
});
}
$scope.create = function () {
// Copy the config before transforming fields to the remote API format
$('#create-modal').modal('hide');
ViewSpinner.spin();
var config = angular.copy($scope.config);
if (config.Cmd && config.Cmd[0] === "[") {
config.Cmd = angular.fromJson(config.Cmd);
} else if (config.Cmd) {
config.Cmd = config.Cmd.split(' ');
}
config.Env = config.Env.map(function (envar) {
return envar.name + '=' + envar.value;
});
var labels = {};
config.Labels = config.Labels.forEach(function(label) {
labels[label.key] = label.value;
});
config.Labels = labels;
config.Volumes = getNames(config.Volumes);
config.SecurityOpts = getNames(config.SecurityOpts);
config.HostConfig.VolumesFrom = getNames(config.HostConfig.VolumesFrom);
config.HostConfig.Binds = getNames(config.HostConfig.Binds);
config.HostConfig.Links = getNames(config.HostConfig.Links);
config.HostConfig.Dns = getNames(config.HostConfig.Dns);
config.HostConfig.DnsSearch = getNames(config.HostConfig.DnsSearch);
config.HostConfig.CapAdd = getNames(config.HostConfig.CapAdd);
config.HostConfig.CapDrop = getNames(config.HostConfig.CapDrop);
config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function (prev, cur, idx) {
prev[cur.name] = cur.value;
return prev;
}, {});
config.HostConfig.ExtraHosts = config.HostConfig.ExtraHosts.map(function (entry) {
return entry.host + ':' + entry.ip;
});
var ExposedPorts = {};
var PortBindings = {};
config.HostConfig.PortBindings.forEach(function (portBinding) {
var intPort = portBinding.intPort + "/tcp";
if (portBinding.protocol === "udp") {
intPort = portBinding.intPort + "/udp";
}
var binding = {
HostIp: portBinding.ip,
HostPort: portBinding.extPort
};
if (portBinding.intPort) {
ExposedPorts[intPort] = {};
if (intPort in PortBindings) {
PortBindings[intPort].push(binding);
} else {
PortBindings[intPort] = [binding];
}
} else {
Messages.send('Warning', 'Internal port must be specified for PortBindings');
}
});
config.ExposedPorts = ExposedPorts;
config.HostConfig.PortBindings = PortBindings;
// Remove empty fields from the request to avoid overriding defaults
rmEmptyKeys(config.HostConfig);
rmEmptyKeys(config);
var ctor = Container;
var s = $scope;
Container.create(config, function (d) {
if (d.Id) {
var reqBody = config.HostConfig || {};
reqBody.id = d.Id;
ctor.start(reqBody, function (cd) {
if (cd.id) {
ViewSpinner.stop();
Messages.send('Container Started', d.Id);
$state.go('container', {id: d.Id}, {reload: true});
} else {
ViewSpinner.stop();
failedRequestHandler(cd, Messages);
ctor.remove({id: d.Id}, function () {
Messages.send('Container Removed', d.Id);
});
}
}, function (e) {
ViewSpinner.stop();
failedRequestHandler(e, Messages);
});
} else {
ViewSpinner.stop();
failedRequestHandler(d, Messages);
}
}, function (e) {
ViewSpinner.stop();
failedRequestHandler(e, Messages);
});
};
$scope.config = {
Env: [],
Labels: [],
Volumes: [],
SecurityOpts: [],
HostConfig: {
PortBindings: [],
Binds: [],
Links: [],
Dns: [],
DnsSearch: [],
VolumesFrom: [],
CapAdd: [],
CapDrop: [],
Devices: [],
LxcConf: [],
ExtraHosts: []
}
};
$scope.menuStatus = {
containerOpen: true,
hostConfigOpen: false
};
function failedRequestHandler(e, Messages) {
Messages.error('Error', errorMsgFilter(e));
}
function rmEmptyKeys(col) {
for (var key in col) {
if (col[key] === null || col[key] === undefined || col[key] === '' || ($.isPlainObject(col[key]) && $.isEmptyObject(col[key])) || col[key].length === 0) {
delete col[key];
}
}
}
function getNames(arr) {
return arr.map(function (item) {
return item.name;
});
}
$scope.create = function () {
// Copy the config before transforming fields to the remote API format
var config = angular.copy($scope.config);
config.Image = $routeParams.id;
if (config.Cmd && config.Cmd[0] === "[") {
config.Cmd = angular.fromJson(config.Cmd);
} else if (config.Cmd) {
config.Cmd = config.Cmd.split(' ');
}
config.Env = config.Env.map(function (envar) {
return envar.name + '=' + envar.value;
});
var labels = {};
config.Labels = config.Labels.forEach(function(label) {
labels[label.key] = label.value;
});
config.Labels = labels;
config.Volumes = getNames(config.Volumes);
config.SecurityOpts = getNames(config.SecurityOpts);
config.HostConfig.VolumesFrom = getNames(config.HostConfig.VolumesFrom);
config.HostConfig.Binds = getNames(config.HostConfig.Binds);
config.HostConfig.Links = getNames(config.HostConfig.Links);
config.HostConfig.Dns = getNames(config.HostConfig.Dns);
config.HostConfig.DnsSearch = getNames(config.HostConfig.DnsSearch);
config.HostConfig.CapAdd = getNames(config.HostConfig.CapAdd);
config.HostConfig.CapDrop = getNames(config.HostConfig.CapDrop);
config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function (prev, cur, idx) {
prev[cur.name] = cur.value;
return prev;
}, {});
config.HostConfig.ExtraHosts = config.HostConfig.ExtraHosts.map(function (entry) {
return entry.host + ':' + entry.ip;
});
var ExposedPorts = {};
var PortBindings = {};
config.HostConfig.PortBindings.forEach(function (portBinding) {
var intPort = portBinding.intPort + "/tcp";
if (portBinding.protocol === "udp") {
intPort = portBinding.intPort + "/udp";
}
var binding = {
HostIp: portBinding.ip,
HostPort: portBinding.extPort
};
if (portBinding.intPort) {
ExposedPorts[intPort] = {};
if (intPort in PortBindings) {
PortBindings[intPort].push(binding);
} else {
PortBindings[intPort] = [binding];
}
} else {
Messages.send('Warning', 'Internal port must be specified for PortBindings');
}
});
config.ExposedPorts = ExposedPorts;
config.HostConfig.PortBindings = PortBindings;
// Remove empty fields from the request to avoid overriding defaults
rmEmptyKeys(config.HostConfig);
rmEmptyKeys(config);
var ctor = Container;
var loc = $location;
var s = $scope;
Container.create(config, function (d) {
if (d.Id) {
var reqBody = config.HostConfig || {};
reqBody.id = d.Id;
ctor.start(reqBody, function (cd) {
if (cd.id) {
Messages.send('Container Started', d.Id);
$('#create-modal').modal('hide');
loc.path('/containers/' + d.Id + '/');
} else {
failedRequestHandler(cd, Messages);
ctor.remove({id: d.Id}, function () {
Messages.send('Container Removed', d.Id);
});
}
}, function (e) {
failedRequestHandler(e, Messages);
});
} else {
failedRequestHandler(d, Messages);
}
}, function (e) {
failedRequestHandler(e, Messages);
});
};
$scope.addEntry = function (array, entry) {
array.push(entry);
};
$scope.rmEntry = function (array, entry) {
var idx = array.indexOf(entry);
array.splice(idx, 1);
};
}]);
$scope.addEntry = function (array, entry) {
array.push(entry);
};
$scope.rmEntry = function (array, entry) {
var idx = array.indexOf(entry);
array.splice(idx, 1);
};
}]);

View file

@ -3,7 +3,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Create And Start Container From Image</h3>
<h3>Start a new container</h3>
</div>
<div class="modal-body">
<form role="form">
@ -12,6 +12,11 @@
<fieldset>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Image:</label>
<input type="text" placeholder='ubuntu:latest'
ng-model="config.Image" class="form-control"/>
</div>
<div class="form-group">
<label>Cmd:</label>
<input type="text" placeholder='["/bin/echo", "Hello world"]'
@ -45,19 +50,18 @@
</div>
<div class="form-group">
<label>Volumes:</label>
<div ng-repeat="volume in config.Volumes">
<div class="form-group form-inline">
<input type="text" ng-model="volume.name" class="form-control"
placeholder="/var/data"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.Volumes, volume)">Remove
</button>
<a href="" ng-click="rmEntry(config.Volumes, volume)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.Volumes, {name: ''})">Add Volume
</button>
<a href="" ng-click="addEntry(config.Volumes, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="col-xs-6">
@ -109,14 +113,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="opt.name" class="form-control"
placeholder="label:type:svirt_apache"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.SecurityOpts, opt)">Remove
</button>
<a href="" ng-click="rmEntry(config.SecurityOpts, opt)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.SecurityOpts, {name: ''})">Add Option
</button>
<a href="" ng-click="addEntry(config.SecurityOpts, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
@ -137,16 +141,15 @@
placeholder="value"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.Env, envar)">Remove
</button>
<a href="" ng-click="rmEntry(config.Env, envar)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.Env, {name: '', value: ''})">Add environment
variable
</button>
<a href="" ng-click="addEntry(config.Env, {name: '', value: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>Labels:</label>
@ -164,15 +167,15 @@
placeholder="value"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.Labels, label)">Remove
</button>
<a href="" ng-click="rmEntry(config.Labels, label)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.Labels, {key: '', value: ''})">Add Label
</button>
<a href="" ng-click="addEntry(config.Labels, {key: '', value: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
</fieldset>
</accordion-group>
@ -187,14 +190,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="bind.name" class="form-control"
placeholder="/host:/container"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.Binds, bind)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.Binds, bind)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.Binds, {name: ''})">Add Bind
</button>
<a href="" ng-click="addEntry(config.HostConfig.Binds, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>Links:</label>
@ -203,14 +206,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="link.name" class="form-control"
placeholder="web:db">
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.Links, link)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.Links, link)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.Links, {name: ''})">Add Link
</button>
<a href="" ng-click="addEntry(config.HostConfig.Links, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>Dns:</label>
@ -219,14 +222,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control"
placeholder="8.8.8.8"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.Dns, entry)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.Dns, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.Dns, {name: ''})">Add entry
</button>
<a href="" ng-click="addEntry(config.HostConfig.Dns, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>DnsSearch:</label>
@ -235,16 +238,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control"
placeholder="example.com"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.DnsSearch, entry)">
Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.DnsSearch, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.DnsSearch, {name: ''})">Add
entry
</button>
<a href="" ng-click="addEntry(config.HostConfig.DnsSearch, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>CapAdd:</label>
@ -253,14 +254,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control"
placeholder="cap_sys_admin"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.CapAdd, entry)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.CapAdd, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.CapAdd, {name: ''})">Add entry
</button>
<a href="" ng-click="addEntry(config.HostConfig.CapAdd, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>CapDrop:</label>
@ -269,14 +270,14 @@
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control"
placeholder="cap_sys_admin"/>
<button type="button" class="btn btn-danger btn-sm"
ng-click="rmEntry(config.HostConfig.CapDrop, entry)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.CapDrop, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.CapDrop, {name: ''})">Add entry
</button>
<a href="" ng-click="addEntry(config.HostConfig.CapDrop, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="col-xs-6">
@ -304,16 +305,14 @@
ng-options="name for name in containerNames track by name"
class="form-control">
</select>
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.HostConfig.VolumesFrom, volume)">
Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.VolumesFrom, volume)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.VolumesFrom, {name: ''})">Add
volume
</button>
<a href="" ng-click="addEntry(config.HostConfig.VolumesFrom, {name: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
@ -346,16 +345,15 @@
placeholder="127.0.0.1"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.HostConfig.ExtraHosts, entry)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.ExtraHosts, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})">Add
extra host
</button>
<a href="" ng-click="addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>LxcConf:</label>
@ -373,16 +371,15 @@
placeholder="docker"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.HostConfig.LxcConf, entry)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.LxcConf, entry)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.LxcConf, {name: '', value: ''})">Add
Entry
</button>
<a href="" ng-click="addEntry(config.HostConfig.LxcConf, {name: '', value: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>Devices:</label>
@ -398,15 +395,14 @@
<label class="sr-only">CgroupPermissions:</label>
<input type="text" ng-model="device.CgroupPermissions" class="form-control"
placeholder="CgroupPermissions"/>
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.HostConfig.Devices, device)">Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.Devices, device)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})">
Add Device
</button>
<a href="" ng-click="addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
<div class="form-group">
<label>PortBindings:</label>
@ -426,16 +422,14 @@
<option value="">tcp</option>
<option value="udp">udp</option>
</select>
<button class="btn btn-danger btn-xs form-control"
ng-click="rmEntry(config.HostConfig.PortBindings, portBinding)">
Remove
</button>
<a href="" ng-click="rmEntry(config.HostConfig.PortBindings, portBinding)">
<i class="fa fa-minus" aria-hidden="true"></i>
</a>
</div>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})">
Add Port Binding
</button>
<a href="" ng-click="addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})">
<i class="fa fa-plus" aria-hidden="true"></i>
</a>
</div>
</fieldset>
</accordion-group>

View file

@ -1,67 +1,67 @@
<div class="row">
<div class="col-xs-12">
<h1>Stats for: {{ containerName }}</h1>
<h2>CPU</h2>
<div class="row">
<div class="col-sm-7">
<canvas id="cpu-stats-chart" width="650" height="300"></canvas>
</div>
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon grey pull-left">
<i class="fa fa-tasks"></i>
</div>
<h2>Memory</h2>
<div class="row">
<div class="col-sm-7">
<canvas id="memory-stats-chart" width="650" height="300"></canvas>
</div>
<div class="col-sm-offset-1 col-sm-4">
<table class="table">
<tr>
<td>Max usage</td>
<td>{{ data.memory_stats.max_usage | humansize }}</td>
</tr>
<tr>
<td>Limit</td>
<td>{{ data.memory_stats.limit | humansize }}</td>
</tr>
<tr>
<td>Fail count</td>
<td>{{ data.memory_stats.failcnt }}</td>
</tr>
</table>
<accordion>
<accordion-group heading="Other stats">
<table class="table">
<tr ng-repeat="(key, value) in data.memory_stats.stats">
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
</table>
</accordion-group>
</accordion>
</div>
<div class="title">{{ containerName }}</div>
<div class="comment">
Name
</div>
<h1>Network {{ networkName}}</h1>
<div class="row">
<div class="col-sm-7">
<canvas id="network-stats-chart" width="650" height="300"></canvas>
</div>
<div class="col-sm-offset-1 col-sm-4">
<div id="network-legend" style="margin-bottom: 20px;"></div>
<accordion>
<accordion-group heading="Other stats">
<table class="table">
<tr ng-repeat="(key, value) in data.network">
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
</table>
</accordion-group>
</accordion>
</div>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-area-chart" title="CPU usage"></rd-widget-header>
<rd-widget-body>
<canvas id="cpu-stats-chart" width="770" height="230"></canvas>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-area-chart" title="Memory usage"></rd-widget-header>
<rd-widget-body>
<canvas id="memory-stats-chart" width="770" height="230"></canvas>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-area-chart" title="Network usage"></rd-widget-header>
<rd-widget-body>
<canvas id="network-stats-chart" width="770" height="230"></canvas>
<div class="comment">
<div id="network-legend" style="margin-bottom: 20px;"></div>
</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Processes"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="title in containerTop.Titles">{{title}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="processInfos in containerTop.Processes">
<td ng-repeat="processInfo in processInfos track by $index">{{processInfo}}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,182 +1,194 @@
angular.module('stats', [])
.controller('StatsController', ['Settings', '$scope', 'Messages', '$timeout', 'Container', '$routeParams', 'humansizeFilter', '$sce', function (Settings, $scope, Messages, $timeout, Container, $routeParams, humansizeFilter, $sce) {
// TODO: Force scale to 0-100 for cpu, fix charts on dashboard,
// TODO: Force memory scale to 0 - max memory
.controller('StatsController', ['Settings', '$scope', 'Messages', '$timeout', 'Container', 'ContainerTop', '$stateParams', 'humansizeFilter', '$sce', '$document',
function (Settings, $scope, Messages, $timeout, Container, ContainerTop, $stateParams, humansizeFilter, $sce, $document) {
// TODO: Force scale to 0-100 for cpu, fix charts on dashboard,
// TODO: Force memory scale to 0 - max memory
$scope.ps_args = '';
$scope.getTop = function () {
ContainerTop.get($stateParams.id, {
ps_args: $scope.ps_args
}, function (data) {
$scope.containerTop = data;
});
};
$document.ready(function(){
var cpuLabels = [];
var cpuData = [];
var memoryLabels = [];
var memoryData = [];
var networkLabels = [];
var networkTxData = [];
var networkRxData = [];
for (var i = 0; i < 20; i++) {
cpuLabels.push('');
cpuData.push(0);
memoryLabels.push('');
memoryData.push(0);
networkLabels.push('');
networkTxData.push(0);
networkRxData.push(0);
}
var cpuDataset = { // CPU Usage
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: cpuData
};
var memoryDataset = {
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: memoryData
};
var networkRxDataset = {
label: "Rx Bytes",
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: networkRxData
};
var networkTxDataset = {
label: "Tx Bytes",
fillColor: "rgba(255,180,174,0.5)",
strokeColor: "rgba(255,180,174,1)",
pointColor: "rgba(255,180,174,1)",
pointStrokeColor: "#fff",
data: networkTxData
};
var networkLegendData = [
{
//value: '',
color: 'rgba(151,187,205,0.5)',
title: 'Rx Data'
},
{
//value: '',
color: 'rgba(255,180,174,0.5)',
title: 'Tx Data'
}
];
var cpuLabels = [];
var cpuData = [];
var memoryLabels = [];
var memoryData = [];
var networkLabels = [];
var networkTxData = [];
var networkRxData = [];
for (var i = 0; i < 20; i++) {
cpuLabels.push('');
cpuData.push(0);
memoryLabels.push('');
memoryData.push(0);
networkLabels.push('');
networkTxData.push(0);
networkRxData.push(0);
}
var cpuDataset = { // CPU Usage
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: cpuData
};
var memoryDataset = {
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: memoryData
};
var networkRxDataset = {
label: "Rx Bytes",
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
data: networkRxData
};
var networkTxDataset = {
label: "Tx Bytes",
fillColor: "rgba(255,180,174,0.5)",
strokeColor: "rgba(255,180,174,1)",
pointColor: "rgba(255,180,174,1)",
pointStrokeColor: "#fff",
data: networkTxData
};
var networkLegendData = [
{
//value: '',
color: 'rgba(151,187,205,0.5)',
title: 'Rx Data'
},
{
//value: '',
color: 'rgba(255,180,174,0.5)',
title: 'Rx Data'
}];
legend($('#network-legend').get(0), networkLegendData);
legend($('#network-legend').get(0), networkLegendData);
Chart.defaults.global.animationSteps = 30; // Lower from 60 to ease CPU load.
var cpuChart = new Chart($('#cpu-stats-chart').get(0).getContext("2d")).Line({
labels: cpuLabels,
datasets: [cpuDataset]
}, {
responsive: true
Chart.defaults.global.animationSteps = 30; // Lower from 60 to ease CPU load.
var cpuChart = new Chart($('#cpu-stats-chart').get(0).getContext("2d")).Line({
labels: cpuLabels,
datasets: [cpuDataset]
}, {
responsive: true
});
var memoryChart = new Chart($('#memory-stats-chart').get(0).getContext('2d')).Line({
labels: memoryLabels,
datasets: [memoryDataset]
},
{
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
},
responsive: true
//scaleOverride: true,
//scaleSteps: 10,
//scaleStepWidth: Math.ceil(initialStats.memory_stats.limit / 10),
//scaleStartValue: 0
});
var networkChart = new Chart($('#network-stats-chart').get(0).getContext("2d")).Line({
labels: networkLabels,
datasets: [networkRxDataset, networkTxDataset]
}, {
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
},
responsive: true
});
$scope.networkLegend = $sce.trustAsHtml(networkChart.generateLegend());
function updateStats() {
Container.stats({id: $stateParams.id}, function (d) {
var arr = Object.keys(d).map(function (key) {
return d[key];
});
var memoryChart = new Chart($('#memory-stats-chart').get(0).getContext('2d')).Line({
labels: memoryLabels,
datasets: [memoryDataset]
},
{
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
},
responsive: true
//scaleOverride: true,
//scaleSteps: 10,
//scaleStepWidth: Math.ceil(initialStats.memory_stats.limit / 10),
//scaleStartValue: 0
});
var networkChart = new Chart($('#network-stats-chart').get(0).getContext("2d")).Line({
labels: networkLabels,
datasets: [networkRxDataset, networkTxDataset]
}, {
scaleLabel: function (valueObj) {
return humansizeFilter(parseInt(valueObj.value, 10));
},
responsive: true
});
$scope.networkLegend = $sce.trustAsHtml(networkChart.generateLegend());
function updateStats() {
Container.stats({id: $routeParams.id}, function (d) {
var arr = Object.keys(d).map(function (key) {
return d[key];
});
if (arr.join('').indexOf('no such id') !== -1) {
Messages.error('Unable to retrieve stats', 'Is this container running?');
return;
}
// Update graph with latest data
$scope.data = d;
updateCpuChart(d);
updateMemoryChart(d);
updateNetworkChart(d);
timeout = $timeout(updateStats, 5000);
}, function () {
Messages.error('Unable to retrieve stats', 'Is this container running?');
timeout = $timeout(updateStats, 5000);
});
if (arr.join('').indexOf('no such id') !== -1) {
Messages.error('Unable to retrieve stats', 'Is this container running?');
return;
}
var timeout;
$scope.$on('$destroy', function () {
$timeout.cancel(timeout);
});
// Update graph with latest data
$scope.data = d;
updateCpuChart(d);
updateMemoryChart(d);
updateNetworkChart(d);
timeout = $timeout(updateStats, 5000);
}, function () {
Messages.error('Unable to retrieve stats', 'Is this container running?');
timeout = $timeout(updateStats, 5000);
});
}
updateStats();
var timeout;
$scope.$on('$destroy', function () {
$timeout.cancel(timeout);
});
function updateCpuChart(data) {
cpuChart.addData([calculateCPUPercent(data)], new Date(data.read).toLocaleTimeString());
cpuChart.removeData();
}
updateStats();
function updateMemoryChart(data) {
memoryChart.addData([data.memory_stats.usage], new Date(data.read).toLocaleTimeString());
memoryChart.removeData();
}
function updateCpuChart(data) {
cpuChart.addData([calculateCPUPercent(data)], new Date(data.read).toLocaleTimeString());
cpuChart.removeData();
}
var lastRxBytes = 0, lastTxBytes = 0;
function updateMemoryChart(data) {
memoryChart.addData([data.memory_stats.usage], new Date(data.read).toLocaleTimeString());
memoryChart.removeData();
}
function updateNetworkChart(data) {
// 1.9+ contains an object of networks, for now we'll just show stats for the first network
// TODO: Show graphs for all networks
if (data.networks) {
$scope.networkName = Object.keys(data.networks)[0];
data.network = data.networks[$scope.networkName];
}
var rxBytes = 0, txBytes = 0;
if (lastRxBytes !== 0 || lastTxBytes !== 0) {
// These will be zero on first call, ignore to prevent large graph spike
rxBytes = data.network.rx_bytes - lastRxBytes;
txBytes = data.network.tx_bytes - lastTxBytes;
}
lastRxBytes = data.network.rx_bytes;
lastTxBytes = data.network.tx_bytes;
networkChart.addData([rxBytes, txBytes], new Date(data.read).toLocaleTimeString());
networkChart.removeData();
}
var lastRxBytes = 0, lastTxBytes = 0;
function calculateCPUPercent(stats) {
// Same algorithm the official client uses: https://github.com/docker/docker/blob/master/api/client/stats.go#L195-L208
var prevCpu = stats.precpu_stats;
var curCpu = stats.cpu_stats;
function updateNetworkChart(data) {
// 1.9+ contains an object of networks, for now we'll just show stats for the first network
// TODO: Show graphs for all networks
if (data.networks) {
$scope.networkName = Object.keys(data.networks)[0];
data.network = data.networks[$scope.networkName];
}
var rxBytes = 0, txBytes = 0;
if (lastRxBytes !== 0 || lastTxBytes !== 0) {
// These will be zero on first call, ignore to prevent large graph spike
rxBytes = data.network.rx_bytes - lastRxBytes;
txBytes = data.network.tx_bytes - lastTxBytes;
}
lastRxBytes = data.network.rx_bytes;
lastTxBytes = data.network.tx_bytes;
networkChart.addData([rxBytes, txBytes], new Date(data.read).toLocaleTimeString());
networkChart.removeData();
}
var cpuPercent = 0.0;
function calculateCPUPercent(stats) {
// Same algorithm the official client uses: https://github.com/docker/docker/blob/master/api/client/stats.go#L195-L208
var prevCpu = stats.precpu_stats;
var curCpu = stats.cpu_stats;
// calculate the change for the cpu usage of the container in between readings
var cpuDelta = curCpu.cpu_usage.total_usage - prevCpu.cpu_usage.total_usage;
// calculate the change for the entire system between readings
var systemDelta = curCpu.system_cpu_usage - prevCpu.system_cpu_usage;
var cpuPercent = 0.0;
if (systemDelta > 0.0 && cpuDelta > 0.0) {
cpuPercent = (cpuDelta / systemDelta) * curCpu.cpu_usage.percpu_usage.length * 100.0;
}
return cpuPercent;
}
// calculate the change for the cpu usage of the container in between readings
var cpuDelta = curCpu.cpu_usage.total_usage - prevCpu.cpu_usage.total_usage;
// calculate the change for the entire system between readings
var systemDelta = curCpu.system_cpu_usage - prevCpu.system_cpu_usage;
Container.get({id: $routeParams.id}, function (d) {
$scope.containerName = d.Name.substring(1);
}, function (e) {
Messages.error("Failure", e.data);
});
}])
;
if (systemDelta > 0.0 && cpuDelta > 0.0) {
cpuPercent = (cpuDelta / systemDelta) * curCpu.cpu_usage.percpu_usage.length * 100.0;
}
return cpuPercent;
}
});
Container.get({id: $stateParams.id}, function (d) {
$scope.containerName = d.Name.substring(1);
}, function (e) {
Messages.error("Failure", e.data);
});
$scope.getTop();
}]);

View file

@ -0,0 +1,130 @@
<div class="row">
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon pull-left">
<i class="fa fa-code"></i>
</div>
<div class="title">{{ docker.Version }}</div>
<div class="comment">Swarm version</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon pull-left">
<i class="fa fa-code"></i>
</div>
<div class="title">{{ docker.ApiVersion }}</div>
<div class="comment">API version</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-3 col-md-6 col-xs-12">
<rd-widget>
<rd-widget-body>
<div class="widget-icon pull-left">
<i class="fa fa-code"></i>
</div>
<div class="title">{{ docker.GoVersion }}</div>
<div class="comment">Go version</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-object-group" title="Cluster status"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Nodes</td>
<td>{{ swarm.Nodes }}</td>
</tr>
<tr>
<td>Containers</td>
<td>{{ info.Containers }}</td>
</tr>
<tr>
<td>Images</td>
<td>{{ info.Images }}</td>
</tr>
<tr>
<td>Strategy</td>
<td>{{ swarm.Strategy }}</td>
</tr>
<tr>
<td>CPUs</td>
<td>{{ info.NCPU }}</td>
</tr>
<tr>
<td>Total Memory</td>
<td>{{ info.MemTotal|humansize }}</td>
</tr>
<tr>
<td>Operating System</td>
<td>{{ info.OperatingSystem }}</td>
</tr>
<tr>
<td>Kernel Version</td>
<td>{{ info.KernelVersion }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-6">
<rd-widget>
<rd-widget-header icon="fa-hdd-o" title="Nodes status"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>
<a href="#/swarm/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/swarm/" ng-click="order('IP')">
IP
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/swarm/" ng-click="order('Containers')">
Containers
<span ng-show="sortType == 'Containers' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Containers' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/swarm/" ng-click="order('Status')">
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="node in (state.filteredNodes = (swarm.Status | filter:state.filter | orderBy:sortType:sortReverse))">
<td>{{ node.name }}</td>
<td>{{ node.ip }}</td>
<td>{{ node.containers }}</td>
<td><span class="label label-{{ node.status|statusbadge }}">{{ node.status }}</span></td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -0,0 +1,62 @@
angular.module('swarm', [])
.controller('SwarmController', ['$scope', 'Info', 'Version', 'Settings',
function ($scope, Info, Version, Settings) {
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.info = {};
$scope.docker = {};
$scope.swarm = {};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
Version.get({}, function (d) {
$scope.docker = d;
});
Info.get({}, function (d) {
$scope.info = d;
extractSwarmInfo(d);
});
function extractSwarmInfo(info) {
// Swarm info is available in SystemStatus object
var systemStatus = info.SystemStatus;
// Swarm strategy
$scope.swarm[systemStatus[1][0]] = systemStatus[1][1];
// Swarm filters
$scope.swarm[systemStatus[2][0]] = systemStatus[2][1];
// Swarm node count
var node_count = parseInt(systemStatus[3][1], 10);
$scope.swarm[systemStatus[3][0]] = node_count;
$scope.swarm.Status = [];
extractNodesInfo(systemStatus, node_count);
}
function extractNodesInfo(info, node_count) {
// First information for node1 available at element #4 of SystemStatus
// The next 10 elements are information related to the node
var node_offset = 4;
for (i = 0; i < node_count; i++) {
extractNodeInfo(info, node_offset);
node_offset += 9;
}
}
function extractNodeInfo(info, offset) {
var node = {};
node.name = info[offset][0];
node.ip = info[offset][1];
node.status = info[offset + 1][1];
node.containers = info[offset + 2][1];
node.cpu = info[offset + 3][1];
node.memory = info[offset + 4][1];
node.labels = info[offset + 5][1];
node.error = info[offset + 6][1];
node.version = info[offset + 8][1];
$scope.swarm.Status.push(node);
}
}]);

View file

@ -1,74 +1,59 @@
<h2>Volumes:</h2>
<div ng-include="template" ng-controller="CreateVolumeController"></div>
<div>
<ul class="nav nav-pills pull-left">
<li class="dropdown">
<a class="dropdown-toggle" id="drop4" role="button" data-toggle="dropdown" data-target="#">Actions <b
class="caret"></b></a>
<ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4">
<li><a tabindex="-1" href="" ng-click="removeAction()">Remove</a></li>
</ul>
</li>
</ul>
<div class="pull-right form-inline">
<input type="text" class="form-control" id="filter" placeholder="Filter" ng-model="filter"/> <label
class="sr-only" for="filter">Filter</label>
</div>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-cubes" title="Volumes">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#create-volume-modal">Create new volume...</button>
</div>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/volumes/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Mountpoint')">
Mountpoint
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in (state.filteredVolumes = (volumes | filter:state.filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
<td>{{ volume.Name|truncate:20 }}</td>
<td>{{ volume.Driver }}</td>
<td>{{ volume.Mountpoint }}</td>
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><label><input type="checkbox" ng-model="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/volumes/" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a href="#/volumes/" ng-click="order('Mountpoint')">
Mountpoint
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in (filteredVolumes = (volumes | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="volume.Checked"/></td>
<td>{{ volume.Name|truncate:20 }}</td>
<td>{{ volume.Driver }}</td>
<td>{{ volume.Mountpoint }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<form role="form" class="">
<div class="form-group">
<label>Name:</label>
<input type="text" placeholder='tardis'
ng-model="createVolumeConfig.Name" class="form-control"/>
</div>
<div class="form-group">
<label>Driver:</label>
<input type="text" placeholder='local'
ng-model="createVolumeConfig.Driver" class="form-control"/>
</div>
<button type="button" class="btn btn-success btn-sm"
ng-click="addVolume(createVolumeConfig)">
Create Volume
</button>
</form>
</div>
</div>

View file

@ -1,80 +1,70 @@
angular.module('volumes', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/volumes/', {
templateUrl: 'app/components/volumes/volumes.html',
controller: 'VolumesController'
angular.module('volumes', [])
.controller('VolumesController', ['$scope', 'Volume', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Volume, ViewSpinner, Messages, $route, errorMsgFilter) {
$scope.state = {};
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.state.filteredVolumes, function (i) {
i.Checked = $scope.state.toggle;
});
}]).controller('VolumesController', ['$scope', 'Volume', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Volume, ViewSpinner, Messages, $route, errorMsgFilter) {
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.createVolumeConfig = {
"Name": "",
"Driver": ""
};
if ($scope.state.toggle) {
$scope.state.selectedItemCount = $scope.state.filteredVolumes.length;
} else {
$scope.state.selectedItemCount = 0;
}
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.volumes, function (volume) {
if (volume.Checked) {
counter = counter + 1;
Volume.remove({name: volume.Name}, function (d) {
Messages.send("Volume deleted", volume.Name);
var index = $scope.volumes.indexOf(volume);
$scope.volumes.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.removeAction = function () {
ViewSpinner.spin();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
ViewSpinner.stop();
}
};
angular.forEach($scope.volumes, function (volume) {
if (volume.Checked) {
counter = counter + 1;
Volume.remove({name: volume.Name}, function (d) {
Messages.send("Volume deleted", volume.Name);
var index = $scope.volumes.indexOf(volume);
$scope.volumes.splice(index, 1);
complete();
}, function (e) {
Messages.error("Failure", e.data);
complete();
});
}
});
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredVolumes, function (i) {
i.Checked = $scope.toggle;
});
};
$scope.addVolume = function addVolume(createVolumeConfig) {
ViewSpinner.spin();
Volume.create(createVolumeConfig, function (d) {
if (d.Name) {
Messages.send("Volume created", d.Name);
} else {
Messages.error('Failure', errorMsgFilter(d));
}
ViewSpinner.stop();
fetchVolumes();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
};
function fetchVolumes() {
ViewSpinner.spin();
Volume.query({}, function (d) {
$scope.volumes = d.Volumes;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchVolumes();
}]);
function fetchVolumes() {
ViewSpinner.spin();
Volume.query({}, function (d) {
$scope.volumes = d.Volumes;
ViewSpinner.stop();
}, function (e) {
Messages.error("Failure", e.data);
ViewSpinner.stop();
});
}
fetchVolumes();
}]);