mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-462: Merge add in-app player for session recordings.
This commit is contained in:
@@ -1365,6 +1365,14 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
*/
|
||||
var extraHeaders = extraTunnelHeaders || {};
|
||||
|
||||
/**
|
||||
* The number of bytes in the file being downloaded, or null if this is not
|
||||
* known.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.size = null;
|
||||
|
||||
this.sendMessage = function sendMessage(elements) {
|
||||
// Do nothing
|
||||
};
|
||||
@@ -1411,6 +1419,9 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
|
||||
}
|
||||
|
||||
// Report overall size of stream in bytes, if known
|
||||
tunnel.size = response.headers.get('Content-Length');
|
||||
|
||||
// Connection is open
|
||||
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
||||
|
||||
|
@@ -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.
|
@@ -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)
|
||||
|
@@ -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',
|
||||
|
@@ -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}
|
||||
*/
|
||||
const 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];
|
||||
|
||||
};
|
||||
|
||||
}]);
|
388
guacamole/src/main/frontend/src/app/player/directives/player.js
Normal file
388
guacamole/src/main/frontend/src/app/player/directives/player.js
Normal 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) {
|
||||
|
||||
const 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.
|
||||
*/
|
||||
const 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;
|
||||
|
||||
}]);
|
@@ -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() {
|
||||
|
||||
const 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}
|
||||
*/
|
||||
const 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}
|
||||
*/
|
||||
const 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;
|
||||
|
||||
}]);
|
@@ -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() {
|
||||
|
||||
const 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;
|
||||
|
||||
}]);
|
52
guacamole/src/main/frontend/src/app/player/playerModule.js
Normal file
52
guacamole/src/main/frontend/src/app/player/playerModule.js
Normal 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'
|
||||
]);
|
147
guacamole/src/main/frontend/src/app/player/styles/player.css
Normal file
147
guacamole/src/main/frontend/src/app/player/styles/player.css
Normal 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;
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
170
guacamole/src/main/frontend/src/app/player/styles/seek.css
Normal file
170
guacamole/src/main/frontend/src/app/player/styles/seek.css
Normal 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;
|
||||
}
|
@@ -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>
|
@@ -0,0 +1,3 @@
|
||||
<div class="guac-player-display" guac-resize="fitDisplay">
|
||||
<div class="guac-player-display-container"></div>
|
||||
</div>
|
@@ -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>
|
@@ -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
|
||||
const authenticationService = $injector.get('authenticationService');
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
|
||||
/**
|
||||
* The URL of the REST API resource exposing the requested session
|
||||
* recording.
|
||||
*
|
||||
* @type {!string}
|
||||
*/
|
||||
const 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()
|
||||
});
|
||||
|
||||
}]);
|
@@ -82,11 +82,11 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function
|
||||
* @type SortOrder
|
||||
*/
|
||||
$scope.order = new SortOrder([
|
||||
'-startDate',
|
||||
'-entry.startDate',
|
||||
'-duration',
|
||||
'username',
|
||||
'connectionName',
|
||||
'remoteHost'
|
||||
'entry.username',
|
||||
'entry.connectionName',
|
||||
'entry.remoteHost'
|
||||
]);
|
||||
|
||||
// Get session date format
|
||||
@@ -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);
|
||||
@@ -216,11 +216,11 @@ angular.module('settings').directive('guacSettingsConnectionHistory', [function
|
||||
),
|
||||
function pushRecord(historyEntryWrapper) {
|
||||
records.push([
|
||||
historyEntryWrapper.username,
|
||||
$filter('date')(historyEntryWrapper.startDate, $scope.dateFormat),
|
||||
historyEntryWrapper.entry.username,
|
||||
$filter('date')(historyEntryWrapper.entry.startDate, $scope.dateFormat),
|
||||
historyEntryWrapper.duration / 1000,
|
||||
historyEntryWrapper.connectionName,
|
||||
historyEntryWrapper.remoteHost
|
||||
historyEntryWrapper.entry.connectionName,
|
||||
historyEntryWrapper.entry.remoteHost
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
@@ -26,6 +26,7 @@ angular.module('settings', [
|
||||
'list',
|
||||
'navigation',
|
||||
'notification',
|
||||
'player',
|
||||
'rest',
|
||||
'storage'
|
||||
]);
|
||||
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -17,31 +17,42 @@
|
||||
<table class="sorted history-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th guac-sort-order="order" guac-sort-property="'username'">
|
||||
<th guac-sort-order="order" guac-sort-property="'entry.username'">
|
||||
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_USERNAME' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="order" guac-sort-property="'startDate'">
|
||||
<th guac-sort-order="order" guac-sort-property="'entry.startDate'">
|
||||
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_STARTDATE' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="order" guac-sort-property="'duration'">
|
||||
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_DURATION' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="order" guac-sort-property="'connectionName'">
|
||||
<th guac-sort-order="order" guac-sort-property="'entry.connectionName'">
|
||||
{{'SETTINGS_CONNECTION_HISTORY.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}}
|
||||
</th>
|
||||
<th guac-sort-order="order" guac-sort-property="'remoteHost'">
|
||||
<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()}">
|
||||
<tr ng-repeat="historyEntryWrapper in historyEntryWrapperPage" class="history">
|
||||
<td><guac-user-item username="historyEntryWrapper.username"></guac-user-item></td>
|
||||
<td>{{historyEntryWrapper.startDate | date : dateFormat}}</td>
|
||||
<td><guac-user-item username="historyEntryWrapper.entry.username"></guac-user-item></td>
|
||||
<td>{{historyEntryWrapper.entry.startDate | date : dateFormat}}</td>
|
||||
<td translate="{{historyEntryWrapper.readableDurationText}}"
|
||||
translate-values="{VALUE: historyEntryWrapper.readableDuration.value, UNIT: historyEntryWrapper.readableDuration.unit}"></td>
|
||||
<td>{{historyEntryWrapper.connectionName}}</td>
|
||||
<td>{{historyEntryWrapper.remoteHost}}</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>
|
||||
|
@@ -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>
|
@@ -24,7 +24,11 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
|
||||
function defineConnectionHistoryEntryWrapper($injector) {
|
||||
|
||||
// Required types
|
||||
var ConnectionHistoryEntry = $injector.get('ConnectionHistoryEntry');
|
||||
const ActivityLog = $injector.get('ActivityLog');
|
||||
const ConnectionHistoryEntry = $injector.get('ConnectionHistoryEntry');
|
||||
|
||||
// Required services
|
||||
const $translate = $injector.get('$translate');
|
||||
|
||||
/**
|
||||
* Wrapper for ConnectionHistoryEntry which adds display-specific
|
||||
@@ -34,55 +38,14 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
|
||||
* @param {ConnectionHistoryEntry} historyEntry
|
||||
* The ConnectionHistoryEntry that should be wrapped.
|
||||
*/
|
||||
var ConnectionHistoryEntryWrapper = function ConnectionHistoryEntryWrapper(historyEntry) {
|
||||
const ConnectionHistoryEntryWrapper = function ConnectionHistoryEntryWrapper(dataSource, historyEntry) {
|
||||
|
||||
/**
|
||||
* The identifier of the connection associated with this history entry.
|
||||
* The wrapped ConnectionHistoryEntry.
|
||||
*
|
||||
* @type String
|
||||
* @type ConnectionHistoryEntry
|
||||
*/
|
||||
this.connectionIdentifier = historyEntry.connectionIdentifier;
|
||||
|
||||
/**
|
||||
* The name of the connection associated with this history entry.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.connectionName = historyEntry.connectionName;
|
||||
|
||||
/**
|
||||
* The remote host associated with this history entry.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.remoteHost = historyEntry.remoteHost;
|
||||
|
||||
/**
|
||||
* The username of the user associated with this particular usage of
|
||||
* the connection.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.username = historyEntry.username;
|
||||
|
||||
/**
|
||||
* The time that usage began, in seconds since 1970-01-01 00:00:00 UTC.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.startDate = historyEntry.startDate;
|
||||
|
||||
/**
|
||||
* The time that usage ended, in seconds since 1970-01-01 00:00:00 UTC.
|
||||
* The absence of an endDate does NOT necessarily indicate that the
|
||||
* connection is still in use, particularly if the server was shutdown
|
||||
* or restarted before the history entry could be updated. To determine
|
||||
* whether a connection is still active, check the active property of
|
||||
* this history entry.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.endDate = historyEntry.endDate;
|
||||
this.entry = historyEntry;
|
||||
|
||||
/**
|
||||
* The total amount of time the connection associated with the wrapped
|
||||
@@ -90,7 +53,7 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.duration = this.endDate - this.startDate;
|
||||
this.duration = historyEntry.endDate - historyEntry.startDate;
|
||||
|
||||
/**
|
||||
* An object providing value and unit properties, denoting the duration
|
||||
@@ -101,7 +64,7 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
|
||||
this.readableDuration = null;
|
||||
|
||||
// Set the duration if the necessary information is present
|
||||
if (this.endDate && this.startDate)
|
||||
if (historyEntry.endDate && historyEntry.startDate)
|
||||
this.readableDuration = new ConnectionHistoryEntry.Duration(this.duration);
|
||||
|
||||
/**
|
||||
@@ -115,9 +78,70 @@ angular.module('settings').factory('ConnectionHistoryEntryWrapper', ['$injector'
|
||||
this.readableDurationText = 'SETTINGS_CONNECTION_HISTORY.TEXT_HISTORY_DURATION';
|
||||
|
||||
// Inform user if end date is not known
|
||||
if (!this.endDate)
|
||||
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;
|
||||
|
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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",
|
||||
|
Reference in New Issue
Block a user