Merge "Add Cloud Shell feature"
This commit is contained in:
commit
17c720878f
1
_0330_cloud_shell_settings.py.sample
Normal file
1
_0330_cloud_shell_settings.py.sample
Normal file
@ -0,0 +1 @@
|
|||||||
|
CLOUD_SHELL_IMAGE = "gbraad/openstack-client:alpine"
|
@ -2,9 +2,19 @@
|
|||||||
Configuration
|
Configuration
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Zun UI has no configuration option.
|
Image for Cloud Shell
|
||||||
|
---------------------
|
||||||
|
|
||||||
For more configurations, see
|
The image for Cloud Shell is set as `gbraad/openstack-client:alpine`
|
||||||
|
by default. If you want to use other image, edit `CLOUD_SHELL_IMAGE`
|
||||||
|
variable in file `_0330_cloud_shell_settings.py.sample`, and copy
|
||||||
|
it to `horizon/openstack_dashboard/local/local_settings.d/_0330_cloud_shell_settings.py`,
|
||||||
|
and restart Horizon.
|
||||||
|
|
||||||
|
For more configurations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
See
|
||||||
`Configuration Guide
|
`Configuration Guide
|
||||||
<https://docs.openstack.org/horizon/latest/configuration/index.html>`__
|
<https://docs.openstack.org/horizon/latest/configuration/index.html>`__
|
||||||
in the Horizon documentation.
|
in the Horizon documentation.
|
||||||
|
@ -208,6 +208,10 @@ def container_attach(request, id):
|
|||||||
return zunclient(request).containers.attach(id)
|
return zunclient(request).containers.attach(id)
|
||||||
|
|
||||||
|
|
||||||
|
def container_resize(request, id, width, height):
|
||||||
|
return zunclient(request).containers.resize(id, width, height)
|
||||||
|
|
||||||
|
|
||||||
def image_list(request, limit=None, marker=None, sort_key=None,
|
def image_list(request, limit=None, marker=None, sort_key=None,
|
||||||
sort_dir=None, detail=True):
|
sort_dir=None, detail=True):
|
||||||
return zunclient(request).images.list(limit, marker, sort_key,
|
return zunclient(request).images.list(limit, marker, sort_key,
|
||||||
|
@ -82,6 +82,10 @@ class ContainerActions(generic.View):
|
|||||||
return client.container_kill(request, id, signal)
|
return client.container_kill(request, id, signal)
|
||||||
elif action == 'attach':
|
elif action == 'attach':
|
||||||
return client.container_attach(request, id)
|
return client.container_attach(request, id)
|
||||||
|
elif action == 'resize':
|
||||||
|
width = request.DATA.get("width") or 500
|
||||||
|
height = request.DATA.get("height") or 400
|
||||||
|
return client.container_resize(request, id, width, height)
|
||||||
|
|
||||||
@rest_utils.ajax(data_required=True)
|
@rest_utils.ajax(data_required=True)
|
||||||
def delete(self, request, id, action):
|
def delete(self, request, id, action):
|
||||||
|
0
zun_ui/content/cloud_shell/__init__.py
Normal file
0
zun_ui/content/cloud_shell/__init__.py
Normal file
27
zun_ui/content/cloud_shell/views.py
Normal file
27
zun_ui/content/cloud_shell/views.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from horizon import views
|
||||||
|
|
||||||
|
|
||||||
|
class CloudShellView(views.HorizonTemplateView):
|
||||||
|
template_name = 'cloud_shell/cloud_shell.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CloudShellView, self).get_context_data(**kwargs)
|
||||||
|
if hasattr(settings, "CLOUD_SHELL_IMAGE"):
|
||||||
|
context['CLOUD_SHELL_IMAGE'] = settings.CLOUD_SHELL_IMAGE
|
||||||
|
else:
|
||||||
|
context['CLOUD_SHELL_IMAGE'] = "gbraad/openstack-client:alpine"
|
||||||
|
return context
|
26
zun_ui/enabled/_0330_cloud_shell.py
Normal file
26
zun_ui/enabled/_0330_cloud_shell.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
FEATURE = True
|
||||||
|
|
||||||
|
ADD_ANGULAR_MODULES = [
|
||||||
|
'horizon.cloud-shell'
|
||||||
|
]
|
||||||
|
|
||||||
|
ADD_SCSS_FILES = [
|
||||||
|
'cloud-shell/cloud-shell.scss'
|
||||||
|
]
|
||||||
|
|
||||||
|
# A list of extensible header views to be displayed
|
||||||
|
ADD_HEADER_SECTIONS = [
|
||||||
|
'zun_ui.content.cloud_shell.views.CloudShellView',
|
||||||
|
]
|
147
zun_ui/static/cloud-shell/cloud-shell.controller.js
Normal file
147
zun_ui/static/cloud-shell/cloud-shell.controller.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.cloud-shell')
|
||||||
|
.controller('horizon.cloud-shell.controller', cloudShellController);
|
||||||
|
|
||||||
|
cloudShellController.$inject = [
|
||||||
|
'$scope',
|
||||||
|
'horizon.app.core.openstack-service-api.zun',
|
||||||
|
'horizon.dashboard.container.webRoot',
|
||||||
|
'horizon.framework.util.http.service'
|
||||||
|
];
|
||||||
|
|
||||||
|
function cloudShellController(
|
||||||
|
$scope,
|
||||||
|
zun,
|
||||||
|
webRoot,
|
||||||
|
http
|
||||||
|
) {
|
||||||
|
var ctrl = this;
|
||||||
|
ctrl.openInNewWindow = openInNewWindow;
|
||||||
|
ctrl.close = closeShell;
|
||||||
|
ctrl.consoleUrl = null;
|
||||||
|
ctrl.container = {};
|
||||||
|
ctrl.resizeTerminal = resizeTerminal;
|
||||||
|
|
||||||
|
// close existing shell
|
||||||
|
closeShell();
|
||||||
|
|
||||||
|
// default size for shell
|
||||||
|
var cols = 80;
|
||||||
|
var rows = 24;
|
||||||
|
|
||||||
|
// get openrc v3 for OpenStack Client
|
||||||
|
var cloudsYaml;
|
||||||
|
http.get('/project/api_access/clouds.yaml/').then(function(response) {
|
||||||
|
// cloud.yaml to be set to .config/openstack/clouds.yaml in container
|
||||||
|
cloudsYaml = response.data;
|
||||||
|
|
||||||
|
ctrl.user = cloudsYaml.match(/username: "(.+)"/)[1];
|
||||||
|
ctrl.project = cloudsYaml.match(/project_name: "(.+)"/)[1];
|
||||||
|
ctrl.userDomain = cloudsYaml.match(/user_domain_name: "(.+)"/);
|
||||||
|
ctrl.projectDomain = cloudsYaml.match(/project_domain_name: "(.+)"/);
|
||||||
|
ctrl.domain = (ctrl.userDomain.length === 2) ? ctrl.userDomain[1] : ctrl.projectDomain[1];
|
||||||
|
ctrl.region = cloudsYaml.match(/region_name: "(.+)"/)[1];
|
||||||
|
|
||||||
|
// container name
|
||||||
|
ctrl.container.name = "cloud-shell-" + ctrl.user + "-" + ctrl.project +
|
||||||
|
"-" + ctrl.domain + "-" + ctrl.region;
|
||||||
|
|
||||||
|
// get container
|
||||||
|
zun.getContainer(ctrl.container.name, true).then(onGetContainer, onFailGetContainer);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onGetContainer(response) {
|
||||||
|
ctrl.container = response.data;
|
||||||
|
|
||||||
|
// attach console to existing container
|
||||||
|
ctrl.consoleUrl = webRoot + "containers/" + ctrl.container.id + "/console";
|
||||||
|
var console = $("<p>To display console, interactive mode needs to be enabled " +
|
||||||
|
"when this container was created.</p>");
|
||||||
|
if (ctrl.container.status !== "Running") {
|
||||||
|
console = $("<p>Container is not running. Please wait for starting up container.</p>");
|
||||||
|
} else if (ctrl.container.interactive) {
|
||||||
|
console = $("<iframe id=\"console_embed\" src=\"" + ctrl.consoleUrl +
|
||||||
|
"\" style=\"width:100%;height:100%\"></iframe>");
|
||||||
|
|
||||||
|
// execute openrc.sh on the container
|
||||||
|
var command = "sh -c 'printf \"" + cloudsYaml + "\" > ~/.config/openstack/clouds.yaml'";
|
||||||
|
zun.executeContainer(ctrl.container.id, {command: command}).then(function() {
|
||||||
|
var command = "sh -c 'printf \"export OS_CLOUD=openstack\" > ~/.bashrc'";
|
||||||
|
zun.executeContainer(ctrl.container.id, {command: command}).then(function() {
|
||||||
|
angular.noop();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// append shell content
|
||||||
|
angular.element("#shell-content").append(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
// watcher for iframe contents loading, seems to emit once.
|
||||||
|
$scope.$watch(function() {
|
||||||
|
return angular.element("#shell-content > iframe").contents()
|
||||||
|
.find("#terminalNode").attr("termCols");
|
||||||
|
}, resizeTerminal);
|
||||||
|
// event handler to resize console according to window resize.
|
||||||
|
angular.element(window).bind('resize', resizeTerminal);
|
||||||
|
// also, add resizeTerminal into callback attribute for resizer directive
|
||||||
|
function resizeTerminal() {
|
||||||
|
var shellIframe = angular.element("#shell-content > iframe");
|
||||||
|
var newCols = shellIframe.contents().find("#terminalNode").attr("termCols");
|
||||||
|
var newRows = shellIframe.contents().find("#terminalNode").attr("termRows");
|
||||||
|
if ((newCols !== cols || newRows !== rows) && newCols > 0 && newRows > 0) {
|
||||||
|
// resize tty
|
||||||
|
zun.resizeContainer(ctrl.container.id, {width: newCols, height: newRows}).then(function() {
|
||||||
|
cols = newCols;
|
||||||
|
rows = newRows;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailGetContainer() {
|
||||||
|
// create new container and attach console to it.
|
||||||
|
var image = angular.element("#cloud-shell-menu").attr("cloud-shell-image");
|
||||||
|
var model = {
|
||||||
|
name: ctrl.container.name,
|
||||||
|
image: image,
|
||||||
|
command: "/bin/bash",
|
||||||
|
interactive: true,
|
||||||
|
run: true,
|
||||||
|
environment: "OS_CLOUD=openstack",
|
||||||
|
labels: "cloud-shell=" + ctrl.container.name
|
||||||
|
};
|
||||||
|
zun.createContainer(model).then(function (response) {
|
||||||
|
// attach
|
||||||
|
onGetContainer({data: {id: response.data.id}});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openInNewWindow() {
|
||||||
|
// open shell in new window
|
||||||
|
window.open(ctrl.consoleUrl, "_blank");
|
||||||
|
closeShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeShell() {
|
||||||
|
// close shell
|
||||||
|
angular.element("#cloud-shell").remove();
|
||||||
|
angular.element("#cloud-shell-resizer").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
23
zun_ui/static/cloud-shell/cloud-shell.html
Normal file
23
zun_ui/static/cloud-shell/cloud-shell.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<div ng-controller="horizon.cloud-shell.controller as ctrl">
|
||||||
|
<resizer id="cloud-shell-resizer"
|
||||||
|
direction="horizontal"
|
||||||
|
height="6"
|
||||||
|
bottom="#cloud-shell"
|
||||||
|
callback="ctrl.resizeTerminal()">
|
||||||
|
</resizer>
|
||||||
|
<div id="cloud-shell">
|
||||||
|
<div id="shell-header">
|
||||||
|
{$ ctrl.container.name $}
|
||||||
|
<!--
|
||||||
|
<a class="cloud-shell-external" ng-click="ctrl.openInNewWindow()">
|
||||||
|
<span class="fa fa-external-link"></span>
|
||||||
|
</a>
|
||||||
|
-->
|
||||||
|
<a class="cloud-shell-close" ng-click="ctrl.close()">
|
||||||
|
<span class="fa fa-times"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="shell-content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
zun_ui/static/cloud-shell/cloud-shell.module.js
Normal file
37
zun_ui/static/cloud-shell/cloud-shell.module.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc overview
|
||||||
|
* @name horizon.cloud-shell
|
||||||
|
* @description
|
||||||
|
* cloud_shell module to host container for cloud shell.
|
||||||
|
*/
|
||||||
|
angular
|
||||||
|
.module('horizon.cloud-shell', [
|
||||||
|
'horizon.cloud-shell.resizer',
|
||||||
|
'ngRoute'
|
||||||
|
])
|
||||||
|
.config(config);
|
||||||
|
|
||||||
|
config.$inject = ['$provide', '$windowProvider'];
|
||||||
|
|
||||||
|
function config($provide, $windowProvider) {
|
||||||
|
var path = $windowProvider.$get().STATIC_URL + 'cloud-shell/';
|
||||||
|
$provide.constant('horizon.cloud-shell.basePath', path);
|
||||||
|
}
|
||||||
|
})();
|
51
zun_ui/static/cloud-shell/cloud-shell.scss
Normal file
51
zun_ui/static/cloud-shell/cloud-shell.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#cloud-shell {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#shell-header {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background-color: gray;
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
|
#shell-content {
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
.cloud-shell-external {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
padding-left: 3px;
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
.cloud-shell-external:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.cloud-shell-close {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
float: right;
|
||||||
|
padding-right: 3px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.cloud-shell-close:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
#cloud-shell-resizer {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
bottom: 200px;
|
||||||
|
height: 6px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: lightgray;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
#cloud-shell-resize-holder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
56
zun_ui/static/cloud-shell/cloud-shell.service.js
Normal file
56
zun_ui/static/cloud-shell/cloud-shell.service.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.cloud-shell')
|
||||||
|
.factory('horizon.cloud-shell.service', cloudShellService);
|
||||||
|
|
||||||
|
cloudShellService.$inject = [
|
||||||
|
'$rootScope',
|
||||||
|
'$templateRequest',
|
||||||
|
'horizon.cloud-shell.basePath'
|
||||||
|
];
|
||||||
|
|
||||||
|
function cloudShellService(
|
||||||
|
$rootScope,
|
||||||
|
$templateRequest,
|
||||||
|
basePath
|
||||||
|
) {
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
// remove existing cloud shell
|
||||||
|
angular.element(".cloud_shell").remove();
|
||||||
|
|
||||||
|
// load html for cloud shell
|
||||||
|
$templateRequest(basePath + 'cloud-shell.html').then(function (html) {
|
||||||
|
var scope = $rootScope.$new();
|
||||||
|
var template = angular.element(html);
|
||||||
|
// compile html
|
||||||
|
angular.element(document.body).injector().invoke(['$compile', function ($compile) {
|
||||||
|
$compile(template)(scope);
|
||||||
|
angular.element('body').append(template);
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
94
zun_ui/static/cloud-shell/resizer.directive.js
Normal file
94
zun_ui/static/cloud-shell/resizer.directive.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.cloud-shell.resizer', [])
|
||||||
|
.directive('resizer', resizer);
|
||||||
|
|
||||||
|
resizer.$inject = ['$document'];
|
||||||
|
|
||||||
|
function resizer($document) {
|
||||||
|
|
||||||
|
var directive = {
|
||||||
|
restrict: 'E',
|
||||||
|
scope: {
|
||||||
|
direction: '@',
|
||||||
|
max: '@',
|
||||||
|
left: '@',
|
||||||
|
right: '@',
|
||||||
|
top: '@',
|
||||||
|
bottom: '@',
|
||||||
|
width: '@',
|
||||||
|
height: '@',
|
||||||
|
callback: '&'
|
||||||
|
},
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
|
||||||
|
return directive;
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
function link($scope, $element) {
|
||||||
|
$element.on('mousedown', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$document.on('mousemove', mousemove);
|
||||||
|
$document.on('mouseup', mouseup);
|
||||||
|
});
|
||||||
|
|
||||||
|
function mousemove(event) {
|
||||||
|
if ($scope.direction === 'vertical') {
|
||||||
|
// Handle vertical resizer
|
||||||
|
var x = event.pageX;
|
||||||
|
|
||||||
|
if ($scope.max && x > $scope.max) {
|
||||||
|
x = parseInt($scope.max, 10);
|
||||||
|
}
|
||||||
|
$element.css({
|
||||||
|
left: x + 'px'
|
||||||
|
});
|
||||||
|
$($scope.left).css({
|
||||||
|
width: x + 'px'
|
||||||
|
});
|
||||||
|
$($scope.right).css({
|
||||||
|
left: (x + parseInt($scope.width, 10)) + 'px'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Handle horizontal resizer
|
||||||
|
var y = window.innerHeight - event.pageY;
|
||||||
|
$element.css({
|
||||||
|
bottom: y + 'px'
|
||||||
|
});
|
||||||
|
$($scope.top).css({
|
||||||
|
bottom: (y + parseInt($scope.height, 10)) + 'px'
|
||||||
|
});
|
||||||
|
$($scope.bottom).css({
|
||||||
|
height: y + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseup() {
|
||||||
|
$document.unbind('mousemove', mousemove);
|
||||||
|
$document.unbind('mouseup', mouseup);
|
||||||
|
if (typeof $scope.callback === "function") {
|
||||||
|
$scope.callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
.console {
|
.console {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
height: 500px;
|
height: calc(100vh - 300px);
|
||||||
}
|
}
|
||||||
textarea#output {
|
textarea#output {
|
||||||
height: 25em;
|
height: 25em;
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
unpauseContainer: unpauseContainer,
|
unpauseContainer: unpauseContainer,
|
||||||
executeContainer: executeContainer,
|
executeContainer: executeContainer,
|
||||||
killContainer: killContainer,
|
killContainer: killContainer,
|
||||||
|
resizeContainer: resizeContainer,
|
||||||
pullImage: pullImage,
|
pullImage: pullImage,
|
||||||
getImages: getImages
|
getImages: getImages
|
||||||
};
|
};
|
||||||
@ -64,9 +65,12 @@
|
|||||||
return apiService.patch(containersPath + id, params).error(error(msg));
|
return apiService.patch(containersPath + id, params).error(error(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContainer(id) {
|
function getContainer(id, suppressError) {
|
||||||
var msg = gettext('Unable to retrieve the Container.');
|
var promise = apiService.get(containersPath + id);
|
||||||
return apiService.get(containersPath + id).error(error(msg));
|
return suppressError ? promise : promise.error(function() {
|
||||||
|
var msg = gettext('Unable to retrieve the Container.');
|
||||||
|
toastService.add('error', msg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContainers() {
|
function getContainers() {
|
||||||
@ -144,6 +148,11 @@
|
|||||||
return apiService.post(containersPath + id + '/kill', params).error(error(msg));
|
return apiService.post(containersPath + id + '/kill', params).error(error(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resizeContainer(id, params) {
|
||||||
|
var msg = gettext('Unable to resize console.');
|
||||||
|
return apiService.post(containersPath + id + '/resize', params).error(error(msg));
|
||||||
|
}
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
// Images //
|
// Images //
|
||||||
////////////
|
////////////
|
||||||
|
10
zun_ui/templates/cloud_shell/cloud_shell.html
Normal file
10
zun_ui/templates/cloud_shell/cloud_shell.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<!-- Menu Item for Extensible Header -->
|
||||||
|
<span
|
||||||
|
id="cloud-shell-menu"
|
||||||
|
class="fa fa-terminal"
|
||||||
|
cloud-shell-image="{{ CLOUD_SHELL_IMAGE }}"
|
||||||
|
onclick="angular.element(document.body).injector().get('horizon.cloud-shell.service').init();">
|
||||||
|
{% trans "Cloud Shell" %}
|
||||||
|
</span>
|
Loading…
x
Reference in New Issue
Block a user