mirror of https://github.com/portainer/portainer
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 examplepull/8/head
parent
1b206f223f
commit
0f51cb66e0
|
@ -4,6 +4,6 @@ logs/*
|
|||
node_modules
|
||||
bower_components
|
||||
.idea
|
||||
dist
|
||||
dockerui
|
||||
*.iml
|
||||
dist
|
||||
dist/*
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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:
|
||||
|
||||
|
|
97
app/app.js
97
app/app.js
|
@ -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');
|
||||
|
|
|
@ -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">×</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>
|
|
@ -1,5 +0,0 @@
|
|||
angular.module('builder', [])
|
||||
.controller('BuilderController', ['$scope',
|
||||
function ($scope) {
|
||||
$scope.template = 'app/components/builder/builder.html';
|
||||
}]);
|
|
@ -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;">×</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>
|
||||
|
|
|
@ -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();
|
||||
}]);
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}]);
|
|
@ -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>
|
||||
<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>
|
||||
|
|
|
@ -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});
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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});
|
||||
};
|
||||
|
||||
}]);
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -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;
|
||||
}]);
|
|
@ -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>
|
|
@ -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();
|
||||
|
||||
}]);
|
|
@ -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;
|
||||
});
|
||||
}]);
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}]);
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
});
|
||||
}]);
|
|
@ -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>
|
|
@ -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();
|
||||
};
|
||||
}]);
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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">×</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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
});
|
||||
}]);
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}]);
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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) {
|
||||
|
|
|
@ -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 |
Binary file not shown.
After Width: | Height: | Size: 180 KiB |
22
bower.json
22
bower.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
965f7ce53139cf9e75e5b8a8206a4af5791eb8f8 dockerui
|
|
@ -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
|
||||
|
|
|
@ -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: .
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
FROM debian
|
||||
|
||||
RUN apt-get update && apt-get install -y socat
|
|
@ -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
|
||||
```
|
|
@ -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
|
39
gruntFile.js
39
gruntFile.js
|
@ -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: {
|
||||
|
|
71
index.html
71
index.html
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue