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

@@ -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

@@ -70,4 +70,20 @@
.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:",
@@ -851,8 +865,9 @@
"SETTINGS_CONNECTION_HISTORY" : {
"ACTION_DOWNLOAD" : "@:APP.ACTION_DOWNLOAD",
"ACTION_SEARCH" : "@:APP.ACTION_SEARCH",
"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",