GUACAMOLE-462: Add in-app player for session recordings.

This commit is contained in:
Michael Jumper
2022-02-10 17:21:50 -08:00
parent 2428c7eb14
commit 531711493e
27 changed files with 1636 additions and 5 deletions

View File

@@ -0,0 +1,19 @@
Copyright (C) 2019 Glyptodon, Inc.
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:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
Session Recording Player for Glyptodon Enterprise
(https://github.com/glyptodon/glyptodon-enterprise-player)
----------------------------------------------------------
Version: 1.1.0-1
From: 'Glyptodon, Inc.' (https://glyptodon.com/)
License(s):
MIT (bundled/glyptodon-enterprise-player/LICENSE)

View File

@@ -180,6 +180,15 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
resolve : { updateCurrentToken: updateCurrentToken }
})
// Recording player
.when('/settings/:dataSource/recording/:identifier/:name', {
title : 'APP.NAME',
bodyClassName : 'settings',
templateUrl : 'app/settings/templates/settingsConnectionHistoryPlayer.html',
controller : 'connectionHistoryPlayerController',
resolve : { updateCurrentToken: updateCurrentToken }
})
// Client view
.when('/client/:id', {
bodyClassName : 'client',

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* Filter which accepts a promise as input, returning the resolved value of
* that promise if/when the promise is resolved. While the promise is not
* resolved, null is returned.
*/
angular.module('index').filter('resolve', [function resolveFilter() {
/**
* The name of the property to use to store the resolved promise value.
*
* @type {!string}
*/
var RESOLVED_VALUE_KEY = '_guac_resolveFilter_resolvedValue';
return function resolveFilter(promise) {
if (!promise)
return null;
// Assign value to RESOLVED_VALUE_KEY automatically upon resolution of
// the received promise
if (!(RESOLVED_VALUE_KEY in promise)) {
promise[RESOLVED_VALUE_KEY] = null;
promise.then((value) => {
return promise[RESOLVED_VALUE_KEY] = value;
});
}
// Always return cached value
return promise[RESOLVED_VALUE_KEY];
};
}]);

View File

@@ -0,0 +1,388 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Directive which plays back session recordings. This directive emits the
* following events based on state changes within the current recording:
*
* "guacPlayerLoading":
* A new recording has been selected and is now loading.
*
* "guacPlayerError":
* The current recording cannot be loaded or played due to an error.
* The recording may be unreadable (lack of permissions) or corrupt
* (protocol error).
*
* "guacPlayerProgress"
* Additional data has been loaded for the current recording and the
* recording's duration has changed. The new duration in milliseconds
* and the number of bytes loaded so far are passed to the event.
*
* "guacPlayerLoaded"
* The current recording has finished loading.
*
* "guacPlayerPlay"
* Playback of the current recording has started or has been resumed.
*
* "guacPlayerPause"
* Playback of the current recording has been paused.
*
* "guacPlayerSeek"
* The playback position of the current recording has changed. The new
* position within the recording is passed to the event as the number
* of milliseconds since the start of the recording.
*/
angular.module('player').directive('guacPlayer', ['$injector', function guacPlayer($injector) {
var config = {
restrict : 'E',
templateUrl : 'app/player/templates/player.html'
};
config.scope = {
/**
* A Blob containing the Guacamole session recording to load.
*
* @type {!Blob|Guacamole.Tunnel}
*/
src : '='
};
config.controller = ['$scope', '$element', '$injector',
function guacPlayerController($scope) {
/**
* Guacamole.SessionRecording instance to be used to playback the
* session recording given via $scope.src. If the recording has not
* yet been loaded, this will be null.
*
* @type {Guacamole.SessionRecording}
*/
$scope.recording = null;
/**
* The current playback position, in milliseconds. If a seek request is
* in progress, this will be the desired playback position of the
* pending request.
*
* @type {!number}
*/
$scope.playbackPosition = 0;
/**
* The key of the translation string that describes the operation
* currently running in the background, or null if no such operation is
* running.
*
* @type {string}
*/
$scope.operationMessage = null;
/**
* The current progress toward completion of the operation running in
* the background, where 0 represents no progress and 1 represents full
* completion. If no such operation is running, this value has no
* meaning.
*
* @type {!number}
*/
$scope.operationProgress = 0;
/**
* The position within the recording of the current seek operation, in
* milliseconds. If a seek request is not in progress, this will be
* null.
*
* @type {number}
*/
$scope.seekPosition = null;
/**
* Whether a seek request is currently in progress. A seek request is
* in progress if the user is attempting to change the current playback
* position (the user is manipulating the playback position slider).
*
* @type {boolean}
*/
var pendingSeekRequest = false;
/**
* Whether playback should be resumed (play() should be invoked on the
* recording) once the current seek request is complete. This value
* only has meaning if a seek request is pending.
*
* @type {boolean}
*/
var resumeAfterSeekRequest = false;
/**
* Formats the given number as a decimal string, adding leading zeroes
* such that the string contains at least two digits. The given number
* MUST NOT be negative.
*
* @param {!number} value
* The number to format.
*
* @returns {!string}
* The decimal string representation of the given value, padded
* with leading zeroes up to a minimum length of two digits.
*/
var zeroPad = function zeroPad(value) {
return value > 9 ? value : '0' + value;
};
/**
* Formats the given quantity of milliseconds as days, hours, minutes,
* and whole seconds, separated by colons (DD:HH:MM:SS). Hours are
* included only if the quantity is at least one hour, and days are
* included only if the quantity is at least one day. All included
* groups are zero-padded to two digits with the exception of the
* left-most group.
*
* @param {!number} value
* The time to format, in milliseconds.
*
* @returns {!string}
* The given quantity of milliseconds formatted as "DD:HH:MM:SS".
*/
$scope.formatTime = function formatTime(value) {
// Round provided value down to whole seconds
value = Math.floor((value || 0) / 1000);
// Separate seconds into logical groups of seconds, minutes,
// hours, etc.
var groups = [ 1, 24, 60, 60 ];
for (var i = groups.length - 1; i >= 0; i--) {
var placeValue = groups[i];
groups[i] = zeroPad(value % placeValue);
value = Math.floor(value / placeValue);
}
// Format groups separated by colons, stripping leading zeroes and
// groups which are entirely zeroes, leaving at least minutes and
// seconds
var formatted = groups.join(':');
return /^[0:]*([0-9]{1,2}(?::[0-9]{2})+)$/.exec(formatted)[1];
};
/**
* Pauses playback and decouples the position slider from current
* playback position, allowing the user to manipulate the slider
* without interference. Playback state will be resumed following a
* call to commitSeekRequest().
*/
$scope.beginSeekRequest = function beginSeekRequest() {
// If a recording is present, pause and save state if we haven't
// already done so
if ($scope.recording && !pendingSeekRequest) {
resumeAfterSeekRequest = $scope.recording.isPlaying();
$scope.recording.pause();
}
// Flag seek request as in progress
pendingSeekRequest = true;
};
/**
* Restores the playback state at the time beginSeekRequest() was
* called and resumes coupling between the playback position slider and
* actual playback position.
*/
$scope.commitSeekRequest = function commitSeekRequest() {
// If a recording is present and there is an active seek request,
// restore the playback state at the time that request began and
// begin seeking to the requested position
if ($scope.recording && pendingSeekRequest) {
$scope.seekPosition = null;
$scope.operationMessage = 'PLAYER.INFO_SEEK_IN_PROGRESS';
$scope.operationProgress = 0;
// Cancel seek when requested, updating playback position if
// that position changed
$scope.cancelOperation = function abortSeek() {
$scope.recording.cancel();
$scope.playbackPosition = $scope.seekPosition || $scope.playbackPosition;
};
resumeAfterSeekRequest && $scope.recording.play();
$scope.recording.seek($scope.playbackPosition, function seekComplete() {
$scope.operationMessage = null;
$scope.$evalAsync();
});
}
// Flag seek request as completed
pendingSeekRequest = false;
};
/**
* Toggles the current playback state. If playback is currently paused,
* playback is resumed. If playback is currently active, playback is
* paused. If no recording has been loaded, this function has no
* effect.
*/
$scope.togglePlayback = function togglePlayback() {
if ($scope.recording) {
if ($scope.recording.isPlaying())
$scope.recording.pause();
else
$scope.recording.play();
}
};
// Automatically load the requested session recording
$scope.$watch('src', function srcChanged(src) {
// Reset position and seek state
pendingSeekRequest = false;
$scope.playbackPosition = 0;
// Stop loading the current recording, if any
if ($scope.recording) {
$scope.recording.pause();
$scope.recording.abort();
}
// If no recording is provided, reset to empty
if (!src)
$scope.recording = null;
// Otherwise, begin loading the provided recording
else {
$scope.recording = new Guacamole.SessionRecording(src);
// Begin downloading the recording
$scope.recording.connect();
// Notify listeners when the recording is completely loaded
$scope.recording.onload = function recordingLoaded() {
$scope.operationMessage = null;
$scope.$emit('guacPlayerLoaded');
$scope.$evalAsync();
};
// Notify listeners if an error occurs
$scope.recording.onerror = function recordingFailed(message) {
$scope.operationMessage = null;
$scope.$emit('guacPlayerError', message);
$scope.$evalAsync();
};
// Notify listeners when additional recording data has been
// loaded
$scope.recording.onprogress = function recordingLoadProgressed(duration, current) {
$scope.operationProgress = src.size ? current / src.size : 0;
$scope.$emit('guacPlayerProgress', duration, current);
$scope.$evalAsync();
};
// Notify listeners when playback has started/resumed
$scope.recording.onplay = function playbackStarted() {
$scope.$emit('guacPlayerPlay');
$scope.$evalAsync();
};
// Notify listeners when playback has paused
$scope.recording.onpause = function playbackPaused() {
$scope.$emit('guacPlayerPause');
$scope.$evalAsync();
};
// Notify listeners when current position within the recording
// has changed
$scope.recording.onseek = function positionChanged(position, current, total) {
// Update current playback position while playing
if ($scope.recording.isPlaying())
$scope.playbackPosition = position;
// Update seek progress while seeking
else {
$scope.seekPosition = position;
$scope.operationProgress = current / total;
}
$scope.$emit('guacPlayerSeek', position);
$scope.$evalAsync();
};
$scope.operationMessage = 'PLAYER.INFO_LOADING_RECORDING';
$scope.operationProgress = 0;
$scope.cancelOperation = function abortLoad() {
$scope.recording.abort();
$scope.operationMessage = null;
};
$scope.$emit('guacPlayerLoading');
}
});
// Clean up resources when player is destroyed
$scope.$on('$destroy', function playerDestroyed() {
$scope.recording.pause();
$scope.recording.abort();
});
}];
return config;
}]);

View File

@@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Directive which contains a given Guacamole.Display, automatically scaling
* the display to fit available space.
*/
angular.module('player').directive('guacPlayerDisplay', [function guacPlayerDisplay() {
var config = {
restrict : 'E',
templateUrl : 'app/player/templates/playerDisplay.html'
};
config.scope = {
/**
* The Guacamole.Display instance which should be displayed within the
* directive.
*
* @type {Guacamole.Display}
*/
display : '='
};
config.controller = ['$scope', '$element', function guacPlayerDisplayController($scope, $element) {
/**
* The root element of this instance of the guacPlayerDisplay
* directive.
*
* @type {Element}
*/
var element = $element.find('.guac-player-display')[0];
/**
* The element which serves as a container for the root element of the
* Guacamole.Display assigned to $scope.display.
*
* @type {HTMLDivElement}
*/
var container = $element.find('.guac-player-display-container')[0];
/**
* Rescales the Guacamole.Display currently assigned to $scope.display
* such that it exactly fits within this directive's available space.
* If no display is currently assigned or the assigned display is not
* at least 1x1 pixels in size, this function has no effect.
*/
$scope.fitDisplay = function fitDisplay() {
// Ignore if no display is yet present
if (!$scope.display)
return;
var displayWidth = $scope.display.getWidth();
var displayHeight = $scope.display.getHeight();
// Ignore if the provided display is not at least 1x1 pixels
if (!displayWidth || !displayHeight)
return;
// Fit display within available space
$scope.display.scale(Math.min(element.offsetWidth / displayWidth,
element.offsetHeight / displayHeight));
};
// Automatically add/remove the Guacamole.Display as $scope.display is
// updated
$scope.$watch('display', function displayChanged(display, oldDisplay) {
// Clear out old display, if any
if (oldDisplay) {
container.innerHTML = '';
oldDisplay.onresize = null;
}
// If a new display is provided, add it to the container, keeping
// its scale in sync with changes to available space and display
// size
if (display) {
container.appendChild(display.getElement());
display.onresize = $scope.fitDisplay;
$scope.fitDisplay();
}
});
}];
return config;
}]);

View File

@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Directive which displays an indicator showing the current progress of an
* arbitrary operation.
*/
angular.module('player').directive('guacPlayerProgressIndicator', [function guacPlayerProgressIndicator() {
var config = {
restrict : 'E',
templateUrl : 'app/player/templates/progressIndicator.html'
};
config.scope = {
/**
* A value between 0 and 1 inclusive which indicates current progress,
* where 0 represents no progress and 1 represents finished.
*
* @type {Number}
*/
progress : '='
};
config.controller = ['$scope', function guacPlayerProgressIndicatorController($scope) {
/**
* The current progress of the operation as a percentage. This value is
* automatically updated as $scope.progress changes.
*
* @type {Number}
*/
$scope.percentage = 0;
/**
* The CSS transform which should be applied to the bar portion of the
* progress indicator. This value is automatically updated as
* $scope.progress changes.
*
* @type {String}
*/
$scope.barTransform = null;
// Keep percentage and bar transform up-to-date with changes to
// progress value
$scope.$watch('progress', function progressChanged(progress) {
progress = progress || 0;
$scope.percentage = Math.floor(progress * 100);
$scope.barTransform = 'rotate(' + (360 * progress - 45) + 'deg)';
});
}];
return config;
}]);

View File

@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Module providing in-browser playback of session recordings.
*/
angular.module('player', [
'element'
]);

View File

@@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
guac-player {
display: inline-block;
position: relative;
}
guac-player .guac-player-display {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
guac-player .guac-player-controls {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
.guac-player-controls .guac-player-seek {
display: block;
width: 100%;
}
.guac-player-controls .guac-player-play,
.guac-player-controls .guac-player-pause {
color: white;
background: transparent;
border: none;
width: 2em;
height: 2em;
min-width: 0;
padding: 0;
margin: 0;
}
.guac-player-controls .guac-player-play:hover,
.guac-player-controls .guac-player-pause:hover {
background: rgba(255, 255, 255, 0.5);
}
.guac-player-controls .pause-icon,
.guac-player-controls .play-icon {
display: inline-block;
width: 2em;
height: 2em;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
vertical-align: middle;
}
.guac-player-controls .play-icon {
background-image: url('images/action-icons/guac-play.svg');
}
.guac-player-controls .pause-icon {
background-image: url('images/action-icons/guac-pause.svg');
}
guac-player .guac-player-status {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-moz-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}

View File

@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.guac-player-display {
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-moz-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.guac-player-display .guac-player-display-container {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-moz-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}

View File

@@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
guac-player-progress-indicator {
width: 128px;
height: 128px;
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-moz-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
guac-player-progress-indicator .guac-player-progress-text {
font-size: 2em;
font-weight: bold;
}
guac-player-progress-indicator .guac-player-progress-bar-container {
position: absolute;
right: 0;
top: 0;
width: 50%;
height: 100%;
overflow: hidden;
}
guac-player-progress-indicator .guac-player-progress-bar-container.past-halfway {
overflow: visible;
}
guac-player-progress-indicator .guac-player-progress-bar-container.past-halfway::before,
guac-player-progress-indicator .guac-player-progress-bar {
position: absolute;
left: -64px;
top: 0;
width: 128px;
height: 128px;
-webkit-border-radius: 128px;
-moz-border-radius: 128px;
border-radius: 128px;
border: 12px solid #5AF;
border-bottom-color: transparent;
border-right-color: transparent;
}
guac-player-progress-indicator .guac-player-progress-bar-container.past-halfway::before {
content: ' ';
display: block;
box-sizing: border-box;
-webkit-transform: rotate(135deg);
-moz-transform: rotate(135deg);
-ms-transform: rotate(135deg);
-o-transform: rotate(135deg);
transform: rotate(135deg);
}

View File

@@ -0,0 +1,170 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
* General (not browser-specific)
*/
input[type="range"] {
background: transparent;
width: 100%;
margin: 0;
}
input[type="range"]:focus {
outline: none;
}
/*
* WebKit
*/
input[type="range"] {
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-runnable-track {
border: none;
border-radius: 0;
background: #5AF;
width: 100%;
height: 0.5em;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
border: none;
border-radius: 0;
background: white;
width: 3px;
height: 0.5em;
-webkit-appearance: none;
cursor: pointer;
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background: #5AF;
}
/*
* Firefox
*/
input[type="range"]::-moz-range-track {
border: none;
border-radius: 0;
background: #5AF;
width: 100%;
height: 0.5em;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
border: none;
border-radius: 0;
background: white;
width: 3px;
height: 0.5em;
cursor: pointer;
}
/*
* Internet Explorer
*/
input[type="range"]::-ms-track {
width: 100%;
height: 0.5em;
margin: 0;
border: none;
border-radius: 0;
background: transparent;
color: transparent;
cursor: pointer;
}
input[type="range"]::-ms-thumb {
border: none;
border-radius: 0;
background: white;
width: 3px;
height: 0.5em;
margin: 0;
cursor: pointer;
}
input[type="range"]::-ms-fill-lower,
input[type="range"]::-ms-fill-upper,
input[type="range"]:focus::-ms-fill-lower,
input[type="range"]:focus::-ms-fill-upper {
border: none;
border-radius: 0;
background: #5AF;
}

View File

@@ -0,0 +1,41 @@
<!-- Actual playback display -->
<guac-player-display display="recording.getDisplay()"
ng-click="togglePlayback()"></guac-player-display>
<!-- Player controls -->
<div class="guac-player-controls" ng-show="recording">
<!-- Playback position slider -->
<input class="guac-player-seek" type="range" min="0" step="1"
ng-attr-max="{{ recording.getDuration() }}"
ng-change="beginSeekRequest()"
ng-model="playbackPosition"
ng-on-change="commitSeekRequest()">
<!-- Play button -->
<button class="guac-player-play"
ng-attr-title="{{ 'PLAYER.ACTION_PLAY' | translate }}"
ng-click="recording.play()"
ng-hide="recording.isPlaying()"><span class="play-icon"></span></button>
<!-- Pause button -->
<button class="guac-player-pause"
ng-attr-title="{{ 'PLAYER.ACTION_PAUSE' | translate }}"
ng-click="recording.pause()"
ng-show="recording.isPlaying()"><span class="pause-icon"></span></button>
<!-- Playback position and duration -->
<span class="guac-player-position">
{{ formatTime(playbackPosition) }} / {{ formatTime(recording.getDuration()) }}
</span>
</div>
<!-- Modal status indicator -->
<div class="guac-player-status" ng-show="operationMessage">
<guac-player-progress-indicator progress="operationProgress"></guac-player-progress-indicator>
<p translate="{{ operationMessage }}"></p>
<button class="guac-player-button guac-player-cancel"
ng-show="cancelOperation"
ng-click="cancelOperation()">{{ 'PLAYER.ACTION_CANCEL' | translate }}</button>
</div>

View File

@@ -0,0 +1,3 @@
<div class="guac-player-display" guac-resize="fitDisplay">
<div class="guac-player-display-container"></div>
</div>

View File

@@ -0,0 +1,12 @@
<div class="guac-player-progress-text">{{ percentage }}%</div>
<div class="guac-player-progress-bar-container" ng-class="{
'past-halfway' : progress > 0.5
}">
<div class="guac-player-progress-bar" ng-style="{
'-webkit-transform' : barTransform,
'-moz-transform' : barTransform,
'-ms-transform' : barTransform,
'-o-transform' : barTransform,
'transform' : barTransform
}"></div>
</div>

View File

@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* The controller for the session recording player page.
*/
angular.module('manage').controller('connectionHistoryPlayerController', ['$scope', '$injector',
function connectionHistoryPlayerController($scope, $injector) {
// Required services
var authenticationService = $injector.get('authenticationService');
var $routeParams = $injector.get('$routeParams');
/**
* The URL of the REST API resource exposing the requested session
* recording.
*
* @type {!string}
*/
var recordingURL = 'api/session/data/' + encodeURIComponent($routeParams.dataSource)
+ '/history/connections/' + encodeURIComponent($routeParams.identifier)
+ '/logs/' + encodeURIComponent($routeParams.name);
/**
* The tunnel which should be used to download the Guacamole session
* recording.
*
* @type Guacamole.Tunnel
*/
$scope.tunnel = new Guacamole.StaticHTTPTunnel(recordingURL, false, {
'Guacamole-Token' : authenticationService.getCurrentToken()
});
}]);

View File

@@ -175,7 +175,7 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function
// Wrap all history entries for sake of display
$scope.historyEntryWrappers = [];
angular.forEach(historyEntries, function wrapHistoryEntry(historyEntry) {
$scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper(historyEntry));
$scope.historyEntryWrappers.push(new ConnectionHistoryEntryWrapper($scope.dataSource, historyEntry));
});
}, requestService.DIE);

View File

@@ -26,6 +26,7 @@ angular.module('settings', [
'list',
'navigation',
'notification',
'player',
'rest',
'storage'
]);

View File

@@ -0,0 +1,114 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*
* NOTE: This session recording player implementation is based on the Session
* Recording Player for Glyptodon Enterprise which is available at
* https://github.com/glyptodon/glyptodon-enterprise-player under the
* following license:
*
* Copyright (C) 2019 Glyptodon, Inc.
*
* 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:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.settings.connectionHistoryPlayer {
background: black;
color: white;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: 0;
margin: 0;
}
.settings.connectionHistoryPlayer guac-player {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.settings.connectionHistoryPlayer .guac-player-help-no-recording,
.settings.connectionHistoryPlayer .guac-player-help-recording-error {
margin: 8px;
max-width: 480px;
}
.settings.connectionHistoryPlayer .guac-player-button {
display: inline-block;
border: 2px solid white;
border-radius: 0;
background: black;
color: white;
font-weight: bold;
padding: 0.5em 1em;
margin: 8px;
}
.settings.connectionHistoryPlayer .guac-player-controls {
padding: 0.25em;
}
.settings.connectionHistoryPlayer .guac-player-controls {
background: rgba(0, 0, 0, 0.5);
}
.settings.connectionHistoryPlayer.playing .guac-player-controls {
opacity: 0;
-webkit-transition: opacity 0.25s linear 0.25s;
-moz-transition: opacity 0.25s linear 0.25s;
-o-transition: opacity 0.25s linear 0.25s;
transition: opacity 0.25s linear 0.25s;
}
.settings.connectionHistoryPlayer.paused .guac-player-controls,
.settings.connectionHistoryPlayer.playing:hover .guac-player-controls {
opacity: 1;
-webkit-transition-delay: 0s;
-moz-transition-delay: 0s;
-o-transition-delay: 0s;
transition-delay: 0s;
}

View File

@@ -71,3 +71,19 @@
.settings.connectionHistory .history-list {
width: 100%;
}
.settings.connectionHistory a.history-session-recording {
color: #0000ee;
}
.settings.connectionHistory a.history-session-recording::after {
display: inline-block;
content: ' ';
width: 1em;
height: 1em;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
background-image: url('images/action-icons/guac-play-link.svg');
vertical-align: middle;
}

View File

@@ -32,6 +32,9 @@
<th guac-sort-order="order" guac-sort-property="'entry.remoteHost'">
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_REMOTEHOST' | translate}}
</th>
<th>
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_LOGS' | translate}}
</th>
</tr>
</thead>
<tbody ng-class="{loading: !isLoaded()}">
@@ -42,6 +45,14 @@
translate-values="{VALUE: historyEntryWrapper.readableDuration.value, UNIT: historyEntryWrapper.readableDuration.unit}"></td>
<td>{{historyEntryWrapper.entry.connectionName}}</td>
<td>{{historyEntryWrapper.entry.remoteHost}}</td>
<td>
<a class="history-session-recording"
ng-show="historyEntryWrapper.sessionRecording"
ng-href="{{ historyEntryWrapper.sessionRecording.url }}"
ng-attr-title="{{ historyEntryWrapper.sessionRecording.description | resolve }}">
<span class="history-action-description">{{'SETTINGS_CONNECTION_HISTORY.ACTION_VIEW_RECORDING' | translate}}</span>
</a>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,11 @@
<guac-viewport class="settings view connectionHistoryPlayer"
ng-class="{
'no-recording' : !selectedRecording,
'paused' : !playing,
'playing' : playing
}">
<!-- Player for selected recording -->
<guac-player src="tunnel"></guac-player>
</guac-viewport>

View File

@@ -24,8 +24,12 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
function defineConnectionHistoryEntryWrapper($injector) {
// Required types
var ActivityLog = $injector.get('ActivityLog');
var ConnectionHistoryEntry = $injector.get('ConnectionHistoryEntry');
// Required services
var $translate = $injector.get('$translate');
/**
* Wrapper for ConnectionHistoryEntry which adds display-specific
* properties, such as a duration.
@@ -34,7 +38,7 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
* @param {ConnectionHistoryEntry} historyEntry
* The ConnectionHistoryEntry that should be wrapped.
*/
var ConnectionHistoryEntryWrapper = function ConnectionHistoryEntryWrapper(historyEntry) {
var ConnectionHistoryEntryWrapper = function ConnectionHistoryEntryWrapper(dataSource, historyEntry) {
/**
* The wrapped ConnectionHistoryEntry.
@@ -77,6 +81,67 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
if (!historyEntry.endDate)
this.readableDurationText = 'SETTINGS_CONNECTION_HISTORY.INFO_CONNECTION_DURATION_UNKNOWN';
/**
* The graphical session recording associated with this history entry,
* if any. If no session recordings are associated with the entry, this
* will be null. If there are multiple session recordings, this will be
* the first such recording.
*
* @type {ConnectionHistoryEntryWrapper.Log}
*/
this.sessionRecording = (function getSessionRecording() {
var identifier = historyEntry.identifier;
if (!identifier)
return null;
var name = _.findKey(historyEntry.logs, log => log.type === ActivityLog.Type.GUACAMOLE_SESSION_RECORDING);
if (!name)
return null;
var log = historyEntry.logs[name];
return new ConnectionHistoryEntryWrapper.Log({
url : '#/settings/' + encodeURIComponent(dataSource)
+ '/recording/' + encodeURIComponent(identifier)
+ '/' + encodeURIComponent(name),
description : $translate(log.description.key, log.description.variables)
});
})();
};
/**
* Representation of the ActivityLog of a ConnectionHistoryEntry which adds
* display-specific properties, such as a URL for viewing the log.
*
* @param {ConnectionHistoryEntryWrapper.Log|Object} [template={}]
* The object whose properties should be copied within the new
* ConnectionHistoryEntryWrapper.Log.
*/
ConnectionHistoryEntryWrapper.Log = function Log(template) {
// Use empty object by default
template = template || {};
/**
* The relative URL for a session recording player that loads the
* session recording represented by this log.
*
* @type {!string}
*/
this.url = template.url;
/**
* A promise that resolves with a human-readable description of the log.
*
* @type {!Promise.<string>}
*/
this.description = template.description;
};
return ConnectionHistoryEntryWrapper;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path style="stroke-width:.87949842;fill:#fff" d="M20 16h8v32h-8zm16 0h8v32h-8z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path style="opacity:1;fill:#00e;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" d="m88.625 45.491-16.88-29.237h33.76z" transform="matrix(0 .94788 .54726 0 15.105 -52.005)"/></svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path style="opacity:1;fill:#fff;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0" d="m88.625 45.491-16.88-29.237h33.76z" transform="matrix(0 .94788 .54726 0 15.105 -52.005)"/></svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@@ -25,11 +25,14 @@
"ACTION_MANAGE_USER_GROUPS" : "Groups",
"ACTION_NAVIGATE_BACK" : "Back",
"ACTION_NAVIGATE_HOME" : "Home",
"ACTION_PAUSE" : "Pause",
"ACTION_PLAY" : "Play",
"ACTION_SAVE" : "Save",
"ACTION_SEARCH" : "Search",
"ACTION_SHARE" : "Share",
"ACTION_UPDATE_PASSWORD" : "Update Password",
"ACTION_VIEW_HISTORY" : "History",
"ACTION_VIEW_RECORDING" : "View",
"DIALOG_HEADER_ERROR" : "Error",
@@ -386,6 +389,17 @@
},
"PLAYER" : {
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
"ACTION_PAUSE" : "@:APP.ACTION_PAUSE",
"ACTION_PLAY" : "@:APP.ACTION_PLAY",
"INFO_LOADING_RECORDING" : "Your recording is now being loaded. Please wait...",
"INFO_SEEK_IN_PROGRESS" : "Seeking to the requested position. Please wait..."
},
"PROTOCOL_KUBERNETES" : {
"FIELD_HEADER_BACKSPACE" : "Backspace key sends:",
@@ -853,6 +867,7 @@
"ACTION_DOWNLOAD" : "@:APP.ACTION_DOWNLOAD",
"ACTION_SEARCH" : "@:APP.ACTION_SEARCH",
"ACTION_VIEW_RECORDING" : "@:APP.ACTION_VIEW_RECORDING",
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
@@ -867,6 +882,7 @@
"TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name",
"TABLE_HEADER_SESSION_DURATION" : "Duration",
"TABLE_HEADER_SESSION_LOGS" : "Logs",
"TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host",
"TABLE_HEADER_SESSION_STARTDATE" : "Start time",
"TABLE_HEADER_SESSION_USERNAME" : "Username",