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
pull/8/head
Anthony Lapenna 2016-06-02 17:34:03 +12:00
parent 1b206f223f
commit 0f51cb66e0
71 changed files with 2790 additions and 3211 deletions

4
.gitignore vendored
View File

@ -4,6 +4,6 @@ logs/*
node_modules
bower_components
.idea
dist
dockerui
*.iml
dist
dist/*

View File

@ -1,4 +1,4 @@
UI For Docker: Copyright (c) 2013-2016 Michael Crosby (crosbymichael.com), Kevan Ahlquist (kevanahlquist.com)
UI For Docker: Copyright (c) 2013-2016 Michael Crosby (crosbymichael.com), Kevan Ahlquist (kevanahlquist.com), Anthony Lapenna (anthonylapenna at cloudinovasi dot id)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,76 +1,92 @@
angular.module('uifordocker', [
'uifordocker.templates',
'ui.bootstrap',
'ui.router',
'ui.select',
'ngCookies',
'ngRoute',
'ngSanitize',
'dockerui.services',
'dockerui.filters',
'masthead',
'footer',
'dashboard',
'container',
'containers',
'containersNetwork',
'images',
'image',
'pullImage',
'startContainer',
'sidebar',
'info',
'builder',
'containerLogs',
'containerTop',
'events',
'stats',
'swarm',
'network',
'networks',
'volumes'])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
'createNetwork',
'volumes',
'createVolume'])
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
'use strict';
$httpProvider.defaults.xsrfCookieName = 'csrfToken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
$routeProvider.when('/', {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('index', {
url: '/',
templateUrl: 'app/components/dashboard/dashboard.html',
controller: 'DashboardController'
});
$routeProvider.when('/containers/', {
})
.state('containers', {
url: '/containers/',
templateUrl: 'app/components/containers/containers.html',
controller: 'ContainersController'
});
$routeProvider.when('/containers/:id/', {
})
.state('container', {
url: "^/containers/:id",
templateUrl: 'app/components/container/container.html',
controller: 'ContainerController'
});
$routeProvider.when('/containers/:id/logs/', {
templateUrl: 'app/components/containerLogs/containerlogs.html',
controller: 'ContainerLogsController'
});
$routeProvider.when('/containers/:id/top', {
templateUrl: 'app/components/containerTop/containerTop.html',
controller: 'ContainerTopController'
});
$routeProvider.when('/containers/:id/stats', {
})
.state('stats', {
url: "^/containers/:id/stats",
templateUrl: 'app/components/stats/stats.html',
controller: 'StatsController'
});
$routeProvider.when('/containers_network', {
templateUrl: 'app/components/containersNetwork/containersNetwork.html',
controller: 'ContainersNetworkController'
});
$routeProvider.when('/images/', {
})
.state('logs', {
url: "^/containers/:id/logs",
templateUrl: 'app/components/containerLogs/containerlogs.html',
controller: 'ContainerLogsController'
})
.state('images', {
url: '/images/',
templateUrl: 'app/components/images/images.html',
controller: 'ImagesController'
});
$routeProvider.when('/images/:id*/', {
})
.state('image', {
url: '^/images/:id/',
templateUrl: 'app/components/image/image.html',
controller: 'ImageController'
})
.state('networks', {
url: '/networks/',
templateUrl: 'app/components/networks/networks.html',
controller: 'NetworksController'
})
.state('network', {
url: '^/networks/:id/',
templateUrl: 'app/components/network/network.html',
controller: 'NetworkController'
})
.state('volumes', {
url: '/volumes/',
templateUrl: 'app/components/volumes/volumes.html',
controller: 'VolumesController'
})
.state('swarm', {
url: '/swarm/',
templateUrl: 'app/components/swarm/swarm.html',
controller: 'SwarmController'
});
$routeProvider.when('/info', {templateUrl: 'app/components/info/info.html', controller: 'InfoController'});
$routeProvider.when('/events', {
templateUrl: 'app/components/events/events.html',
controller: 'EventsController'
});
$routeProvider.otherwise({redirectTo: '/'});
// The Docker API likes to return plaintext errors, this catches them and disp
$httpProvider.interceptors.push(function() {
@ -92,8 +108,9 @@ angular.module('uifordocker', [
};
});
}])
// This is your docker url that the api will use to make requests
// You need to set this to the api endpoint without the port i.e. http://192.168.1.9
.constant('DOCKER_ENDPOINT', 'dockerapi')
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243
.constant('UI_VERSION', 'v0.10.1-beta');
.constant('UI_VERSION', 'v0.11.0');

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,72 +1,91 @@
<div class="detail">
<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 ng-if="!container.edit">
<h4>Container: {{ container.Name }}
<button class="btn btn-primary"
ng-click="container.edit = true;">Rename
</button>
</h4>
<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 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 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="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">
<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>Created</td>
<td>{{ container.Created | date: 'medium' }}</td>
</tr>
<tr>
<td>Path:</td>
<td>Path</td>
<td>{{ container.Path }}</td>
</tr>
<tr>
<td>Args:</td>
<td>
<pre>{{ container.Args.join(' ') || 'None' }}</pre>
</td>
<td>Args</td>
<td>{{ container.Args.join(' ') || 'None' }}</td>
</tr>
<tr>
<td>Exposed Ports:</td>
<td>Exposed Ports</td>
<td>
<ul>
<li ng-repeat="(k, v) in container.Config.ExposedPorts">{{ k }}</li>
@ -74,55 +93,17 @@
</td>
</tr>
<tr>
<td>Environment:</td>
<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>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>
@ -130,169 +111,76 @@
</table>
</td>
</tr>
<tr>
<td>Publish All:</td>
<td>Publish all ports</td>
<td>{{ container.HostConfig.PublishAllPorts }}</td>
</tr>
<tr>
<td>Ports:</td>
<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>Hostname</td>
<td>{{ container.Config.Hostname }}</td>
</tr>
<tr>
<td>IPAddress:</td>
<td>IPAddress</td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Cmd:</td>
<td>Cmd</td>
<td>{{ container.Config.Cmd }}</td>
</tr>
<tr>
<td>Entrypoint:</td>
<td>
<pre>{{ container.Config.Entrypoint.join(' ') }}</pre>
</td>
<td>Entrypoint</td>
<td>{{ container.Config.Entrypoint.join(' ') }}</td>
</tr>
<tr>
<td>Bindings:</td>
<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>Volumes</td>
<td>{{ container.Volumes }}</td>
</tr>
<tr>
<td>SysInitpath:</td>
<td>SysInitpath</td>
<td>{{ container.SysInitPath }}</td>
</tr>
<tr>
<td>Image:</td>
<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>
<div class="span5">
<i class="icon-refresh" style="width:32px;height:32px;" ng-click="getChanges()"></i>
</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>
</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,6 +1,6 @@
angular.module('container', [])
.controller('ContainerController', ['$scope', '$routeParams', '$location', 'Container', 'ContainerCommit', 'Image', 'Messages', 'ViewSpinner', '$timeout',
function ($scope, $routeParams, $location, Container, ContainerCommit, Image, Messages, ViewSpinner, $timeout) {
.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;
@ -12,10 +12,10 @@
var update = function () {
ViewSpinner.spin();
Container.get({id: $routeParams.id}, function (d) {
Container.get({id: $stateParams.id}, function (d) {
$scope.container = d;
$scope.container.edit = false;
$scope.container.newContainerName = d.Name;
$scope.container.newContainerName = $filter('trimcontainername')(d.Name);
// fill up env
if (d.Config.Env) {
@ -81,7 +81,7 @@
HostConfig: $scope.container.HostConfig
}, function (d) {
update();
Messages.send("Container started", $routeParams.id);
Messages.send("Container started", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to start." + e.data);
@ -90,9 +90,9 @@
$scope.stop = function () {
ViewSpinner.spin();
Container.stop({id: $routeParams.id}, function (d) {
Container.stop({id: $stateParams.id}, function (d) {
update();
Messages.send("Container stopped", $routeParams.id);
Messages.send("Container stopped", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to stop." + e.data);
@ -101,123 +101,20 @@
$scope.kill = function () {
ViewSpinner.spin();
Container.kill({id: $routeParams.id}, function (d) {
Container.kill({id: $stateParams.id}, function (d) {
update();
Messages.send("Container killed", $routeParams.id);
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);
config.Env = $scope.newCfg.Env.map(function(entry) {
return entry.name+"="+entry.value;
});
var portBindings = angular.copy($scope.newCfg.Ports);
angular.forEach(portBindings, function(item, key) {
if (item.length === 0) {
delete portBindings[key];
}
});
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);
}
}
});
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 = '';
}
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);
});
}
}, 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) {
ContainerCommit.commit({id: $stateParams.id, repo: $scope.container.Config.Image}, function (d) {
update();
Messages.send("Container commited", $routeParams.id);
Messages.send("Container commited", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to commit." + e.data);
@ -225,9 +122,9 @@
};
$scope.pause = function () {
ViewSpinner.spin();
Container.pause({id: $routeParams.id}, function (d) {
Container.pause({id: $stateParams.id}, function (d) {
update();
Messages.send("Container paused", $routeParams.id);
Messages.send("Container paused", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to pause." + e.data);
@ -236,9 +133,9 @@
$scope.unpause = function () {
ViewSpinner.spin();
Container.unpause({id: $routeParams.id}, function (d) {
Container.unpause({id: $stateParams.id}, function (d) {
update();
Messages.send("Container unpaused", $routeParams.id);
Messages.send("Container unpaused", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to unpause." + e.data);
@ -247,10 +144,10 @@
$scope.remove = function () {
ViewSpinner.spin();
Container.remove({id: $routeParams.id}, function (d) {
Container.remove({id: $stateParams.id}, function (d) {
update();
$location.path('/containers');
Messages.send("Container removed", $routeParams.id);
$state.go('containers', {}, {reload: true});
Messages.send("Container removed", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to remove." + e.data);
@ -259,9 +156,9 @@
$scope.restart = function () {
ViewSpinner.spin();
Container.restart({id: $routeParams.id}, function (d) {
Container.restart({id: $stateParams.id}, function (d) {
update();
Messages.send("Container restarted", $routeParams.id);
Messages.send("Container restarted", $stateParams.id);
}, function (e) {
update();
Messages.error("Failure", "Container failed to restart." + e.data);
@ -274,7 +171,7 @@
$scope.getChanges = function () {
ViewSpinner.spin();
Container.changes({id: $routeParams.id}, function (d) {
Container.changes({id: $stateParams.id}, function (d) {
$scope.changes = d;
ViewSpinner.stop();
});
@ -282,10 +179,10 @@
$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) {
Container.rename({id: $stateParams.id, 'name': $scope.container.newContainerName}, function (data) {
if (data.name) {
$scope.container.Name = data.name;
Messages.send("Container renamed", $routeParams.id);
Messages.send("Container renamed", $stateParams.id);
} else {
$scope.container.newContainerName = $scope.container.Name;
Messages.error("Failure", "Container failed to rename.");
@ -309,4 +206,3 @@
update();
$scope.getChanges();
}]);

View File

@ -1,13 +1,15 @@
angular.module('containerLogs', [])
.controller('ContainerLogsController', ['$scope', '$routeParams', '$location', '$anchorScroll', 'ContainerLogs', 'Container', 'ViewSpinner',
function ($scope, $routeParams, $location, $anchorScroll, ContainerLogs, Container, ViewSpinner) {
.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.showTimestamps = false;
$scope.tailLines = 2000;
ViewSpinner.spin();
Container.get({id: $routeParams.id}, function (d) {
Container.get({id: $stateParams.id}, function (d) {
$scope.container = d;
ViewSpinner.stop();
}, function (e) {
@ -21,25 +23,16 @@ angular.module('containerLogs', [])
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;
getLogsStdout();
getLogsStderr();
ViewSpinner.stop();
});
}
ContainerLogs.get($routeParams.id, {
function getLogsStderr() {
ContainerLogs.get($stateParams.id, {
stdout: 0,
stderr: 1,
timestamps: $scope.showTimestamps,
timestamps: $scope.state.displayTimestampsErr,
tail: $scope.tailLines
}, function (data, status, headers, config) {
// Replace carriage returns with newlines to clean up output
@ -48,7 +41,22 @@ angular.module('containerLogs', [])
data = data.substring(8);
data = data.replace(/\n(.{8})/g, '\n');
$scope.stderr = data;
ViewSpinner.stop();
});
}
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;
});
}
@ -61,16 +69,11 @@ angular.module('containerLogs', [])
clearInterval(logIntervalId);
});
$scope.scrollTo = function (id) {
$location.hash(id);
$anchorScroll();
$scope.toggleTimestampsOut = function () {
getLogsStdout();
};
$scope.toggleTimestamps = function () {
getLogs();
};
$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>
<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 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="title">{{ container.Name|trimcontainername }}</div>
<div class="comment">Name</div>
</rd-widget-body>
</rd-widget>
</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="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="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 id="stderr" class="panel-title">STDERR</h3>
</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>
</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,31 +1,33 @@
<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 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>
<table class="table table-striped">
<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="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/containers/" ng-click="order('Names')">
Name
@ -64,9 +66,9 @@
</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>
<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>
@ -74,3 +76,7 @@
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>

View File

@ -1,10 +1,13 @@
angular.module('containers', [])
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner',
function ($scope, Container, Settings, Messages, ViewSpinner) {
$scope.state = {};
$scope.state.displayAll = Settings.displayAll;
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.displayAll = Settings.displayAll;
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
@ -13,9 +16,12 @@ angular.module('containers', [])
var update = function (data) {
ViewSpinner.spin();
$scope.state.selectedItemCount = 0;
Container.query(data, function (d) {
$scope.containers = d.map(function (item) {
return new ContainerViewModel(item);
$scope.containers = d.filter(function (container) {
return container.Image !== 'swarm';
}).map(function (container) {
return new ContainerViewModel(container);
});
ViewSpinner.stop();
});
@ -75,14 +81,28 @@ angular.module('containers', [])
}
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredContainers, function (i) {
i.Checked = $scope.toggle;
$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.toggleGetAll = function () {
Settings.displayAll = $scope.displayAll;
Settings.displayAll = $scope.state.displayAll;
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 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 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 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 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-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">
<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>
<div id="chart-legend"></div>
</rd-widget-body>
</rd-widget>
</div>
</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">
<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>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,10 +1,10 @@
angular.module('dashboard', [])
.controller('DashboardController', ['$scope', 'Container', 'Image', 'Settings', 'LineChart', function ($scope, Container, Image, Settings, LineChart) {
$scope.predicate = '-Created';
$scope.containers = [];
var getStarted = function (data) {
$scope.totalContainers = data.length;
$scope.containerData = {};
var buildCharts = function (data) {
$scope.containerData.total = data.length;
LineChart.build('#containers-started-chart', data, function (c) {
return new Date(c.Created * 1000).toLocaleDateString();
});
@ -17,59 +17,30 @@ angular.module('dashboard', [])
});
};
var opts = {animation: false};
if (Settings.firstLoad) {
opts.animation = true;
Settings.firstLoad = false;
localStorage.setItem('firstLoad', false);
$('#masthead').show();
setTimeout(function () {
$('#masthead').slideUp('slow');
}, 5000);
}
Container.query({all: 1}, function (d) {
var running = 0;
var ghost = 0;
var stopped = 0;
for (var i = 0; i < d.length; i++) {
var item = d[i];
// TODO: centralize that
var containers = d.filter(function (container) {
return container.Image !== 'swarm';
});
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.containers.push(new ContainerViewModel(item));
}
}
$scope.containerData.running = running;
$scope.containerData.stopped = stopped;
$scope.containerData.ghost = ghost;
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,29 +1,48 @@
<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="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 class="title">{{ id }}</div>
<div class="comment">Image ID</div>
</rd-widget-body>
</rd-widget>
</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">
<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>Tags:</td>
<td>Created</td>
<td>{{ image.Created | date: 'medium'}}</td>
</tr>
<tr>
<td>Tags</td>
<td>
<ul>
<li ng-repeat="tag in RepoTags">{{ tag }}
@ -33,88 +52,41 @@
</td>
</tr>
<tr>
<td>Created:</td>
<td>{{ image.Created | date: 'medium'}}</td>
</tr>
<tr>
<td>Parent:</td>
<td>Parent</td>
<td><a href="#/images/{{ image.Parent }}/">{{ image.Parent }}</a></td>
</tr>
<tr>
<td>Size (Virtual Size):</td>
<td>Size (Virtual Size)</td>
<td>{{ image.Size|humansize }} ({{ image.VirtualSize|humansize }})</td>
</tr>
<tr>
<td>Hostname:</td>
<td>Hostname</td>
<td>{{ image.ContainerConfig.Hostname }}</td>
</tr>
<tr>
<td>User:</td>
<td>User</td>
<td>{{ image.ContainerConfig.User }}</td>
</tr>
<tr>
<td>Cmd:</td>
<td>Cmd</td>
<td>{{ image.ContainerConfig.Cmd }}</td>
</tr>
<tr>
<td>Volumes:</td>
<td>Volumes</td>
<td>{{ image.ContainerConfig.Volumes }}</td>
</tr>
<tr>
<td>Volumes from:</td>
<td>Volumes from</td>
<td>{{ image.ContainerConfig.VolumesFrom }}</td>
</tr>
<tr>
<td>Built with:</td>
<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>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,7 +1,6 @@
angular.module('image', [])
.controller('ImageController', ['$scope', '$q', '$routeParams', '$location', 'Image', 'Container', 'Messages', 'LineChart',
function ($scope, $q, $routeParams, $location, Image, Container, Messages, LineChart) {
$scope.history = [];
.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 = [];
@ -14,9 +13,9 @@ angular.module('image', [])
});
// 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');
$state.go('images', {}, {reload: true});
} else {
$location.path('/images/' + $scope.id); // Refresh the current page.
$state.go('image', {id: $scope.id}, {reload: true});
}
}, function (e) {
$scope.error = e.data;
@ -24,45 +23,6 @@ angular.module('image', [])
});
};
$scope.getHistory = function () {
Image.history({id: $routeParams.id}, function (d) {
$scope.history = d;
});
};
$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
@ -78,7 +38,7 @@ angular.module('image', [])
});
}
Image.get({id: $routeParams.id}, function (d) {
Image.get({id: $stateParams.id}, function (d) {
$scope.image = d;
$scope.id = d.Id;
if (d.RepoTags) {
@ -86,21 +46,13 @@ angular.module('image', [])
} 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;
$scope.error = "Image not found.<br />" + $stateParams.id;
} else {
$scope.error = e.data;
}
$('#error-message').show();
});
$scope.getHistory();
}]);

View File

@ -1,27 +1,26 @@
<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 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>
<table class="table table-striped">
<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="toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
<th>
<a href="#/images/" ng-click="order('Id')">
Id
@ -53,8 +52,8 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="image in (filteredImages = (images | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="image.Checked" /></td>
<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>
@ -62,3 +61,7 @@
</tr>
</tbody>
</table>
</div>
</rd-widget-body>
<rd-widget>
</div>

View File

@ -1,17 +1,34 @@
angular.module('images', [])
.controller('ImagesController', ['$scope', 'Image', 'ViewSpinner', 'Messages',
function ($scope, Image, ViewSpinner, Messages) {
$scope.state = {};
$scope.sortType = 'Created';
$scope.sortReverse = true;
$scope.toggle = false;
$scope.state.toggle = false;
$scope.state.selectedItemCount = 0;
$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.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
@ -41,12 +58,7 @@ angular.module('images', [])
});
};
$scope.toggleSelectAll = function () {
angular.forEach($scope.filteredImages, function (i) {
i.Checked = $scope.toggle;
});
};
function fetchImages() {
ViewSpinner.spin();
Image.query({}, function (d) {
$scope.images = d.map(function (item) {
@ -57,4 +69,7 @@ angular.module('images', [])
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,94 +1,110 @@
<div class="detail">
<h4>Network: {{ network.Name }}</h4>
<table class="table table-striped">
<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>Name:</td>
<td>{{ network.Name }}</td>
</tr>
<tr>
<td>Id:</td>
<td>Id</td>
<td>{{ network.Id }}</td>
</tr>
<tr>
<td>Scope:</td>
<td>Scope</td>
<td>{{ network.Scope }}</td>
</tr>
<tr>
<td>Driver:</td>
<td>Driver</td>
<td>{{ network.Driver }}</td>
</tr>
<tr>
<td>IPAM:</td>
<td>IPAM</td>
<td>
<table class="table table-striped">
<tr>
<td>Driver:</td>
<td>Driver</td>
<td>{{ network.IPAM.Driver }}</td>
</tr>
<tr>
<td>Subnet:</td>
<td>Subnet</td>
<td>{{ network.IPAM.Config[0].Subnet }}</td>
</tr>
<tr>
<td>Gateway:</td>
<td>Gateway</td>
<td>{{ network.IPAM.Config[0].Gateway }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Containers:</td>
<td>Containers</td>
<td>
<table class="table table-striped" ng-repeat="(Id, container) in network.Containers">
<tr>
<td>Id:</td>
<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">
</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>
<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>
</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>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>
@ -98,13 +114,7 @@
</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>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,44 +1,25 @@
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', '$routeParams', '$location', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $routeParams, $location, errorMsgFilter) {
}]).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: $routeParams.id}, {Container: containerId}, function (d) {
Network.disconnect({id: $stateParams.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.
$state.go('network', {id: $stateParams.id}, {reload: true});
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
});
};
$scope.remove = function remove(networkId) {
ViewSpinner.spin();
Network.remove({id: $routeParams.id}, function (d) {
Network.remove({id: $stateParams.id}, function (d) {
ViewSpinner.stop();
Messages.send("Network removed", d);
$location.path('/networks'); // Go to the networks page
Messages.send("Network removed", "");
$state.go('networks', {});
}, function (e) {
ViewSpinner.stop();
Messages.error("Failure", e.data);
@ -46,7 +27,7 @@ angular.module('network', []).config(['$routeProvider', function ($routeProvider
};
ViewSpinner.spin();
Network.get({id: $routeParams.id}, function (d) {
Network.get({id: $stateParams.id}, function (d) {
$scope.network = d;
ViewSpinner.stop();
}, function (e) {

View File

@ -1,25 +1,26 @@
<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 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>
<table class="table table-striped">
<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="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/networks/" ng-click="order('Name')">
Name
@ -72,8 +73,8 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="network in ( filteredNetworks = (networks | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="network.Checked"/></td>
<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>
@ -84,38 +85,7 @@
</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>
</rd-widget-body>
<rd-widget>
</div>

View File

@ -1,30 +1,36 @@
angular.module('networks', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/networks/', {
templateUrl: 'app/components/networks/networks.html',
controller: 'NetworksController'
});
}]).controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
function ($scope, Network, ViewSpinner, Messages, $route, errorMsgFilter) {
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.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": ''
}]
$scope.toggleSelectAll = function () {
angular.forEach($scope.state.filteredNetworks, function (i) {
i.Checked = $scope.state.toggle;
});
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();
@ -51,28 +57,6 @@ angular.module('networks', []).config(['$routeProvider', function ($routeProvide
});
};
$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) {

View File

@ -2,34 +2,25 @@
<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>
<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="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."/>
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="Image name"
<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="Tag name. If empty it will download ALL tags."/>
placeholder="Leave empty to download ALL tags."/>
</div>
</form>
</div>

View File

@ -1,12 +1,11 @@
angular.module('pullImage', [])
.controller('PullImageController', ['$scope', '$log', 'Messages', 'Image', 'ViewSpinner',
function ($scope, $log, Messages, Image, ViewSpinner) {
.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'
};
@ -22,7 +21,6 @@ angular.module('pullImage', [])
$('#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 : '');
@ -41,10 +39,12 @@ angular.module('pullImage', [])
} 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();

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,6 +1,6 @@
angular.module('startContainer', ['ui.bootstrap'])
.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter',
function ($scope, $routeParams, $location, Container, Messages, containernameFilter, errorMsgFilter) {
.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) {
@ -54,9 +54,10 @@ angular.module('startContainer', ['ui.bootstrap'])
$scope.create = function () {
// Copy the config before transforming fields to the remote API format
var config = angular.copy($scope.config);
$('#create-modal').modal('hide');
ViewSpinner.spin();
config.Image = $routeParams.id;
var config = angular.copy($scope.config);
if (config.Cmd && config.Cmd[0] === "[") {
config.Cmd = angular.fromJson(config.Cmd);
@ -121,7 +122,6 @@ angular.module('startContainer', ['ui.bootstrap'])
rmEmptyKeys(config);
var ctor = Container;
var loc = $location;
var s = $scope;
Container.create(config, function (d) {
if (d.Id) {
@ -129,22 +129,26 @@ angular.module('startContainer', ['ui.bootstrap'])
reqBody.id = d.Id;
ctor.start(reqBody, function (cd) {
if (cd.id) {
ViewSpinner.stop();
Messages.send('Container Started', d.Id);
$('#create-modal').modal('hide');
loc.path('/containers/' + 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);
});
};

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="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="title">{{ containerName }}</div>
<div class="comment">
Name
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-7">
<canvas id="cpu-stats-chart" width="650" height="300"></canvas>
<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>
<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>
<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 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>
<accordion>
<accordion-group heading="Other stats">
<table class="table">
<tr ng-repeat="(key, value) in data.network">
<td>{{ key }}</td>
<td>{{ value }}</td>
</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>
</accordion-group>
</accordion>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,8 +1,17 @@
angular.module('stats', [])
.controller('StatsController', ['Settings', '$scope', 'Messages', '$timeout', 'Container', '$routeParams', 'humansizeFilter', '$sce', function (Settings, $scope, Messages, $timeout, Container, $routeParams, humansizeFilter, $sce) {
.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 = [];
@ -58,8 +67,10 @@ angular.module('stats', [])
{
//value: '',
color: 'rgba(255,180,174,0.5)',
title: 'Rx Data'
}];
title: 'Tx Data'
}
];
legend($('#network-legend').get(0), networkLegendData);
Chart.defaults.global.animationSteps = 30; // Lower from 60 to ease CPU load.
@ -96,7 +107,7 @@ angular.module('stats', [])
$scope.networkLegend = $sce.trustAsHtml(networkChart.generateLegend());
function updateStats() {
Container.stats({id: $routeParams.id}, function (d) {
Container.stats({id: $stateParams.id}, function (d) {
var arr = Object.keys(d).map(function (key) {
return d[key];
});
@ -172,11 +183,12 @@ angular.module('stats', [])
}
return cpuPercent;
}
});
Container.get({id: $routeParams.id}, function (d) {
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,25 +1,26 @@
<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 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>
<table class="table table-striped">
<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="toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()"/> Select</label></th>
<th>
<a href="#/volumes/" ng-click="order('Name')">
Name
@ -44,31 +45,15 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in (filteredVolumes = (volumes | filter:filter | orderBy:sortType:sortReverse))">
<td><input type="checkbox" ng-model="volume.Checked"/></td>
<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 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>
</rd-widget-body>
<rd-widget>
</div>

View File

@ -1,23 +1,35 @@
angular.module('volumes', []).config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/volumes/', {
templateUrl: 'app/components/volumes/volumes.html',
controller: 'VolumesController'
});
}]).controller('VolumesController', ['$scope', 'Volume', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter',
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.toggle = false;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.createVolumeConfig = {
"Name": "",
"Driver": ""
$scope.toggleSelectAll = function () {
angular.forEach($scope.state.filteredVolumes, function (i) {
i.Checked = $scope.state.toggle;
});
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();
@ -44,28 +56,6 @@ angular.module('volumes', []).config(['$routeProvider', function ($routeProvider
});
};
$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) {

View File

@ -0,0 +1,9 @@
angular
.module('uifordocker')
.directive('rdLoading', function rdLoading() {
var directive = {
restrict: 'AE',
template: '<div class="loading"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>'
};
return directive;
});

View File

@ -0,0 +1,15 @@
angular
.module('uifordocker')
.directive('rdWidgetBody', function rdWidgetBody() {
var directive = {
requires: '^rdWidget',
scope: {
loading: '@?',
classes: '@?'
},
transclude: true,
template: '<div class="widget-body" ng-class="classes"><rd-loading ng-show="loading"></rd-loading><div ng-hide="loading" class="widget-content" ng-transclude></div></div>',
restrict: 'E'
};
return directive;
});

View File

@ -0,0 +1,11 @@
angular
.module('uifordocker')
.directive('rdWidgetFooter', function rdWidgetFooter() {
var directive = {
requires: '^rdWidget',
transclude: true,
template: '<div class="widget-footer" ng-transclude></div>',
restrict: 'E'
};
return directive;
});

View File

@ -0,0 +1,15 @@
angular
.module('uifordocker')
.directive('rdWidgetHeader', function rdWidgetTitle() {
var directive = {
requires: '^rdWidget',
scope: {
title: '@',
icon: '@'
},
transclude: true,
template: '<div class="widget-header"><div class="row"><div class="pull-left"><i class="fa" ng-class="icon"></i> {{title}} </div><div class="pull-right col-xs-6 col-sm-4" ng-transclude></div></div></div>',
restrict: 'E'
};
return directive;
});

View File

@ -0,0 +1,14 @@
angular
.module('uifordocker')
.directive('rdWidgetTaskbar', function rdWidgetTaskbar() {
var directive = {
requires: '^rdWidget',
scope: {
classes: '@?'
},
transclude: true,
template: '<div class="widget-header"><div class="row"><div ng-class="classes" ng-transclude></div></div></div>',
restrict: 'E'
};
return directive;
});

13
app/directives/widget.js Normal file
View File

@ -0,0 +1,13 @@
angular
.module('uifordocker')
.directive('rdWidget', function rdWidget() {
var directive = {
scope: {
"ngModel": "="
},
transclude: true,
template: '<div class="widget" ng-transclude></div>',
restrict: 'EA'
};
return directive;
});

View File

@ -23,12 +23,23 @@ angular.module('dockerui.filters', [])
return function (text) {
if (text === 'Ghost') {
return 'important';
} else if (text === 'Unhealthy') {
return 'danger';
} else if (text.indexOf('Exit') !== -1 && text !== 'Exit 0') {
return 'warning';
}
return 'success';
};
})
.filter('trimcontainername', function () {
'use strict';
return function (name) {
if (name) {
return (name.indexOf('/') === 0 ? name.replace('/','') : name);
}
return '';
};
})
.filter('getstatetext', function () {
'use strict';
return function (state) {

View File

@ -114,3 +114,28 @@
cursor: pointer;
}
.logo {
display: inline;
width: 110px;
max-height: 38px;
}
.containerNameInput {
width: 85%;
border:none;
background:none;
border-bottom: 1px solid black;
}
.containerNameInput:active, .containerNameInput:focus {
outline:none;
}
#network-legend {
text-align: center;
}
#network-legend span {
display: inline;
font-size: 18px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 183 KiB

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -23,20 +23,24 @@
],
"dependencies": {
"Chart.js": "1.0.2",
"angular": "1.3.15",
"angular-sanitize": "1.3.15",
"angular-bootstrap": "0.12.0",
"angular-mocks": "1.3.15",
"angular": "~1.5.0",
"angular-cookies": "~1.5.0",
"angular-bootstrap": "~1.0.3",
"angular-ui-router": "^0.2.15",
"angular-sanitize": "~1.5.0",
"angular-mocks": "~1.5.0",
"angular-oboe": "*",
"angular-resource": "1.3.15",
"angular-route": "1.3.15",
"angular-visjs": "0.0.7",
"bootstrap": "3.3.0",
"angular-resource": "~1.5.0",
"angular-route": "~1.5.0",
"bootstrap": "~3.3.6",
"font-awesome": "~4.5.0",
"jquery": "1.11.1",
"jquery.gritter": "1.7.4",
"lodash": "4.12.0",
"rdash-ui": "1.0.*",
"spin.js": "1.3"
},
"resolutions": {
"angular": "1.3.15"
"angular": "1.5.5"
}
}

BIN
dist/ui-for-docker vendored Executable file

Binary file not shown.

1
dockerui-checksum.txt Normal file
View File

@ -0,0 +1 @@
965f7ce53139cf9e75e5b8a8206a4af5791eb8f8 dockerui

View File

@ -1,4 +1,4 @@
FROM nginx:1.9.9
FROM nginx:latest
COPY default.conf /etc/nginx/conf.d/default.conf
COPY users.htpasswd /etc/nginx/users.htpasswd

View File

@ -1,8 +1,6 @@
dockerui:
image: dockerui/dockerui
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
image: cloudinovasi/ui-for-docker
command: -e http://<SWARM_HOST>:<SWARM_PORT>
nginx:
build: .

View File

@ -1,3 +0,0 @@
FROM debian
RUN apt-get update && apt-get install -y socat

View File

@ -1,11 +0,0 @@
# UI For Docker with Swarm
This example works with swarm clusters created with docker-machine.
## Usage
Make sure your client is pointed directly to the Docker daemon on the swarm-master's node (not through swarm).
```
docker-compose up -d
```

View File

@ -1,11 +0,0 @@
dockerui:
image: uifd/ui-for-docker
command: -e http://127.0.0.1:2375
net: "host"
socat:
build: .
net: "host"
command: socat -d -d TCP-L:2375,fork,bind=localhost ssl:127.0.0.1:3376,cert=/var/lib/boot2docker/server.pem,cafile=/var/lib/boot2docker/ca.pem,key=/var/lib/boot2docker/server-key.pem
volumes:
- /var/lib/boot2docker:/var/lib/boot2docker

View File

@ -29,7 +29,7 @@ module.exports = function (grunt) {
'html2js',
'uglify',
'clean:tmpl',
//'jshint',
'jshint',
//'karma:unit',
'concat:index',
'recess:min',
@ -37,8 +37,9 @@ module.exports = function (grunt) {
]);
grunt.registerTask('test-watch', ['karma:watch']);
grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']);
grunt.registerTask('runSwarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm']);
grunt.registerTask('runSwarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']);
grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
grunt.registerTask('clear', ['clean:app']);
// Print a timestamp (useful for when watching)
grunt.registerTask('timestamp', function () {
@ -68,8 +69,8 @@ module.exports = function (grunt) {
'assets/js/jquery.gritter.js', // Using custom version to fix error in minified build due to "use strict"
'bower_components/bootstrap/dist/js/bootstrap.js',
'bower_components/spin.js/spin.js',
'bower_components/vis/dist/vis.js',
'bower_components/Chart.js/Chart.js',
'bower_components/lodash/dist/lodash.js',
'bower_components/oboe/dist/oboe-browser.js',
'assets/js/legend.js' // Not a bower package
],
@ -81,7 +82,9 @@ module.exports = function (grunt) {
cssVendor: [
'bower_components/bootstrap/dist/css/bootstrap.css',
'bower_components/jquery.gritter/css/jquery.gritter.css',
'bower_components/vis/dist/vis.css'
'bower_components/font-awesome/css/font-awesome.min.css',
'bower_components/rdash-ui/dist/css/rdash.css',
'bower_components/angular-ui-select/dist/select.css'
]
},
clean: {
@ -92,7 +95,9 @@ module.exports = function (grunt) {
copy: {
assets: {
files: [
{dest: '<%= distdir %>/fonts/', src: '**', expand: true, cwd: 'bower_components/bootstrap/fonts/'},
{dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/bootstrap/fonts/'},
{dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/font-awesome/fonts/'},
{dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/rdash-ui/dist/fonts/'},
{
dest: '<%= distdir %>/images/',
src: ['**', '!trees.jpg'],
@ -100,18 +105,10 @@ module.exports = function (grunt) {
cwd: 'bower_components/jquery.gritter/images/'
},
{
dest: '<%= distdir %>/img',
src: [
'network/downArrow.png',
'network/leftArrow.png',
'network/upArrow.png',
'network/rightArrow.png',
'network/minus.png',
'network/plus.png',
'network/zoomExtends.png'
],
dest: '<%= distdir %>/images/',
src: ['**'],
expand: true,
cwd: 'bower_components/vis/dist/img'
cwd: 'assets/images/'
},
{dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'}
]
@ -154,11 +151,13 @@ module.exports = function (grunt) {
angular: {
src: ['bower_components/angular/angular.js',
'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/angular-cookies/angular-cookies.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-ui-router/release/angular-ui-router.js',
'bower_components/angular-resource/angular-resource.js',
'bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'bower_components/angular-oboe/dist/angular-oboe.js',
'bower_components/angular-visjs/angular-vis.js'],
'bower_components/angular-ui-select/dist/select.js'],
dest: '<%= distdir %>/angular.js'
}
},
@ -222,6 +221,10 @@ module.exports = function (grunt) {
* Tried using a host volume with -v, copying files with `docker cp`, restating container, none worked
* Rebuilding image on each change was only method that worked, takes ~4s per change to update
*/
},
buildSwarm: {
files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'],
tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages']
}
},
jshint: {
@ -265,7 +268,7 @@ module.exports = function (grunt) {
command: [
'docker stop ui-for-docker',
'docker rm ui-for-docker',
'docker run --net=host -d --name ui-for-docker ui-for-docker -e http://127.0.0.1:2374'
'docker run --net=host -d --name ui-for-docker ui-for-docker -e http://10.0.7.11:4000'
].join(';')
},
cleanImages: {

View File

@ -2,7 +2,7 @@
<html lang="en" ng-app="<%= pkg.name %>">
<head>
<meta charset="utf-8">
<title>UI For Docker</title>
<title>CloudInovasi UI for Docker</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="<%= pkg.author %>">
@ -10,7 +10,6 @@
<link href="vendor.css" rel="stylesheet">
<link href="<%= pkg.name %>.css" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
@ -25,17 +24,69 @@
<link rel="apple-touch-icon-precomposed" href="ico/apple-touch-icon-precomposed.png">
</head>
<body>
<body ng-controller="MasterCtrl">
<div id="page-wrapper" ng-class="{'open': toggle}" ng-cloak>
<div class="container">
<div ng-include="template" ng-controller="MastheadController"></div>
<div id="view" ng-view></div>
<div class="container-bottom"></div>
<div ng-include="template" ng-controller="FooterController"></div>
<!-- Sidebar -->
<div id="sidebar-wrapper">
<ul class="sidebar">
<li class="sidebar-main">
<a ng-click="toggleSidebar()">
<img src="images/logo.png" class="img-responsive logo" alt="logo">
<span class="menu-icon glyphicon glyphicon-transfer"></span>
</a>
</li>
<li class="sidebar-title"><span>NAVIGATION</span></li>
<li class="sidebar-list">
<a href="#">Dashboard <span class="menu-icon fa fa-tachometer"></span></a>
</li>
<li class="sidebar-list">
<a href="#/containers/">Containers <span class="menu-icon fa fa-server"></span></a>
</li>
<li class="sidebar-list">
<a href="#/images/">Images <span class="menu-icon fa fa-clone"></span></a>
</li>
<li class="sidebar-list">
<a href="#/networks/">Networks <span class="menu-icon fa fa-sitemap"></span></a>
</li>
<li class="sidebar-list">
<a href="#/volumes/">Volumes <span class="menu-icon fa fa-cubes"></span></a>
</li>
<li class="sidebar-list">
<a href="#/swarm/">Swarm <span class="menu-icon fa fa-object-group"></span></a>
</li>
</ul>
<div class="sidebar-footer">
<div class="col-xs-12">
<a href="https://github.com/cloud-inovasi/cloudinovasi-ui" target="_blank">CloudInovasi UI {{ uiVersion }}</a>
</div>
</div>
</div>
<!-- End Sidebar -->
<div id="content-wrapper">
<div class="page-content">
<!-- Header Bar -->
<div class="row header">
<div class="col-xs-12">
<div class="meta">
<div class="page">
Dashboard
</div>
<div class="breadcrumb-links">
Home / Dashboard
</div>
</div>
</div>
</div>
<!-- End Header Bar -->
<!-- Main Content -->
<div id="view" ui-view></div>
</div><!-- End Page Content -->
</div><!-- End Content Wrapper -->
</div><!-- End Page Wrapper -->
</body>
</html>