mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-36: Merge new system for fully translatable error messages.
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which is associated with a translatable message that can be passed
|
||||||
|
* through an arbitrary translation service, producing a human-readable message
|
||||||
|
* in the user's native language.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public interface Translatable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a message which can be translated using a translation service,
|
||||||
|
* consisting of a translation key and optional set of substitution
|
||||||
|
* variables.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A message which can be translated using a translation service.
|
||||||
|
*/
|
||||||
|
TranslatableMessage getTranslatableMessage();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message which can be translated using a translation service, providing a
|
||||||
|
* translation key and optional set of values to be substituted into the
|
||||||
|
* translation string associated with that key.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class TranslatableMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arbitrary key which can be used to look up the message to be
|
||||||
|
* displayed in the user's native language.
|
||||||
|
*/
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrary object whose properties should be substituted for the
|
||||||
|
* corresponding placeholders within the string associated with the key.
|
||||||
|
*/
|
||||||
|
private final Object variables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TranslatableMessage associated with the given translation
|
||||||
|
* key, without any associated variables.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* The translation key to associate with the TranslatableMessage.
|
||||||
|
*/
|
||||||
|
public TranslatableMessage(String key) {
|
||||||
|
this(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TranslatableMessage associated with the given translation
|
||||||
|
* key and associated variables.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* The translation key to associate with the TranslatableMessage.
|
||||||
|
*
|
||||||
|
* @param variables
|
||||||
|
* An arbitrary object whose properties should be substituted for the
|
||||||
|
* corresponding placeholders within the string associated with the
|
||||||
|
* given translation key.
|
||||||
|
*/
|
||||||
|
public TranslatableMessage(String key, Object variables) {
|
||||||
|
this.key = key;
|
||||||
|
this.variables = variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the arbitrary key which can be used to look up the message to be
|
||||||
|
* displayed in the user's native language.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The arbitrary key associated with the human-readable message.
|
||||||
|
*/
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an arbitrary object whose properties should be substituted for
|
||||||
|
* the corresponding placeholders within the string associated with the key.
|
||||||
|
* If not applicable, null is returned.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An arbitrary object whose properties should be substituted for the
|
||||||
|
* corresponding placeholders within the string associated with the key,
|
||||||
|
* or null if not applicable.
|
||||||
|
*/
|
||||||
|
public Object getVariables() {
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,8 +20,17 @@
|
|||||||
package org.apache.guacamole.rest;
|
package org.apache.guacamole.rest;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import javax.ws.rs.core.Response;
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||||
|
import org.apache.guacamole.GuacamoleSecurityException;
|
||||||
import org.apache.guacamole.form.Field;
|
import org.apache.guacamole.form.Field;
|
||||||
|
import org.apache.guacamole.language.Translatable;
|
||||||
|
import org.apache.guacamole.language.TranslatableMessage;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||||
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||||
|
import org.apache.guacamole.tunnel.GuacamoleStreamException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an error that occurred within a REST endpoint.
|
* Describes an error that occurred within a REST endpoint.
|
||||||
@@ -32,10 +41,15 @@ import org.apache.guacamole.form.Field;
|
|||||||
public class APIError {
|
public class APIError {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The error message.
|
* The human-readable error message.
|
||||||
*/
|
*/
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A translatable message representing the error that occurred.
|
||||||
|
*/
|
||||||
|
private final TranslatableMessage translatableMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated Guacamole protocol status code.
|
* The associated Guacamole protocol status code.
|
||||||
*/
|
*/
|
||||||
@@ -60,124 +74,125 @@ public class APIError {
|
|||||||
* The requested operation could not be performed because the request
|
* The requested operation could not be performed because the request
|
||||||
* itself was malformed.
|
* itself was malformed.
|
||||||
*/
|
*/
|
||||||
BAD_REQUEST(Response.Status.BAD_REQUEST),
|
BAD_REQUEST,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The credentials provided were invalid.
|
* The credentials provided were invalid.
|
||||||
*/
|
*/
|
||||||
INVALID_CREDENTIALS(Response.Status.FORBIDDEN),
|
INVALID_CREDENTIALS,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The credentials provided were not necessarily invalid, but were not
|
* The credentials provided were not necessarily invalid, but were not
|
||||||
* sufficient to determine validity.
|
* sufficient to determine validity.
|
||||||
*/
|
*/
|
||||||
INSUFFICIENT_CREDENTIALS(Response.Status.FORBIDDEN),
|
INSUFFICIENT_CREDENTIALS,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An internal server error has occurred.
|
* An internal server error has occurred.
|
||||||
*/
|
*/
|
||||||
INTERNAL_ERROR(Response.Status.INTERNAL_SERVER_ERROR),
|
INTERNAL_ERROR,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object related to the request does not exist.
|
* An object related to the request does not exist.
|
||||||
*/
|
*/
|
||||||
NOT_FOUND(Response.Status.NOT_FOUND),
|
NOT_FOUND,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permission was denied to perform the requested operation.
|
* Permission was denied to perform the requested operation.
|
||||||
*/
|
*/
|
||||||
PERMISSION_DENIED(Response.Status.FORBIDDEN),
|
PERMISSION_DENIED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error occurred within an intercepted stream, terminating that
|
* An error occurred within an intercepted stream, terminating that
|
||||||
* stream. The Guacamole protocol status code of that error can be
|
* stream. The Guacamole protocol status code of that error can be
|
||||||
* retrieved with getStatusCode().
|
* retrieved with getStatusCode().
|
||||||
*/
|
*/
|
||||||
STREAM_ERROR(Response.Status.BAD_REQUEST);
|
STREAM_ERROR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP status associated with this error type.
|
* Returns the REST API error type which corresponds to the type of the
|
||||||
*/
|
* given exception.
|
||||||
private final Response.Status status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a new error type associated with the given HTTP status.
|
|
||||||
*
|
*
|
||||||
* @param status
|
* @param exception
|
||||||
* The HTTP status to associate with the error type.
|
* The exception to use to derive the API error type.
|
||||||
*/
|
|
||||||
Type(Response.Status status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP status associated with this error type.
|
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* The HTTP status associated with this error type.
|
* The API error type which corresponds to the type of the given
|
||||||
|
* exception.
|
||||||
*/
|
*/
|
||||||
public Response.Status getStatus() {
|
public static Type fromGuacamoleException(GuacamoleException exception) {
|
||||||
return status;
|
|
||||||
|
// Additional credentials are needed
|
||||||
|
if (exception instanceof GuacamoleInsufficientCredentialsException)
|
||||||
|
return INSUFFICIENT_CREDENTIALS;
|
||||||
|
|
||||||
|
// The provided credentials are wrong
|
||||||
|
if (exception instanceof GuacamoleInvalidCredentialsException)
|
||||||
|
return INVALID_CREDENTIALS;
|
||||||
|
|
||||||
|
// Generic permission denied
|
||||||
|
if (exception instanceof GuacamoleSecurityException)
|
||||||
|
return PERMISSION_DENIED;
|
||||||
|
|
||||||
|
// Arbitrary resource not found
|
||||||
|
if (exception instanceof GuacamoleResourceNotFoundException)
|
||||||
|
return NOT_FOUND;
|
||||||
|
|
||||||
|
// Arbitrary bad requests
|
||||||
|
if (exception instanceof GuacamoleClientException)
|
||||||
|
return BAD_REQUEST;
|
||||||
|
|
||||||
|
// Errors from intercepted streams
|
||||||
|
if (exception instanceof GuacamoleStreamException)
|
||||||
|
return STREAM_ERROR;
|
||||||
|
|
||||||
|
// All other errors
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new APIError of type STREAM_ERROR and having the given
|
* Creates a new APIError which exposes the details of the given
|
||||||
* Guacamole protocol status code and human-readable message. The status
|
* GuacamoleException. If the given GuacamoleException implements
|
||||||
* code and message should be taken directly from the "ack" instruction
|
* Translatable, then its translation string and values will be exposed as
|
||||||
* causing the error.
|
* well.
|
||||||
*
|
*
|
||||||
* @param statusCode
|
* @param exception
|
||||||
* The Guacamole protocol status code describing the error that
|
* The GuacamoleException from which the details of the new APIError
|
||||||
* occurred within the intercepted stream.
|
* should be derived.
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* An arbitrary human-readable message describing the error that
|
|
||||||
* occurred.
|
|
||||||
*/
|
*/
|
||||||
public APIError(int statusCode, String message) {
|
public APIError(GuacamoleException exception) {
|
||||||
this.type = Type.STREAM_ERROR;
|
|
||||||
this.message = message;
|
// Build base REST service error
|
||||||
this.statusCode = statusCode;
|
this.type = Type.fromGuacamoleException(exception);
|
||||||
|
this.message = exception.getMessage();
|
||||||
|
|
||||||
|
// Add expected credentials if applicable
|
||||||
|
if (exception instanceof GuacamoleCredentialsException) {
|
||||||
|
GuacamoleCredentialsException credentialsException = (GuacamoleCredentialsException) exception;
|
||||||
|
this.expected = credentialsException.getCredentialsInfo().getFields();
|
||||||
|
}
|
||||||
|
else
|
||||||
this.expected = null;
|
this.expected = null;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Add stream status code if applicable
|
||||||
* Create a new APIError with the specified error message.
|
if (exception instanceof GuacamoleStreamException) {
|
||||||
*
|
GuacamoleStreamException streamException = (GuacamoleStreamException) exception;
|
||||||
* @param type
|
this.statusCode = streamException.getStatus().getGuacamoleStatusCode();
|
||||||
* The type of error that occurred.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* The error message.
|
|
||||||
*/
|
|
||||||
public APIError(Type type, String message) {
|
|
||||||
this.type = type;
|
|
||||||
this.message = message;
|
|
||||||
this.statusCode = null;
|
|
||||||
this.expected = null;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/**
|
|
||||||
* Create a new APIError with the specified error message and parameter
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* The type of error that occurred.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* The error message.
|
|
||||||
*
|
|
||||||
* @param expected
|
|
||||||
* All parameters expected in the original request, or now required as
|
|
||||||
* a result of the original request, as a collection of fields.
|
|
||||||
*/
|
|
||||||
public APIError(Type type, String message, Collection<Field> expected) {
|
|
||||||
this.type = type;
|
|
||||||
this.message = message;
|
|
||||||
this.statusCode = null;
|
this.statusCode = null;
|
||||||
this.expected = expected;
|
|
||||||
|
// Pull translatable message and values if available
|
||||||
|
if (exception instanceof Translatable) {
|
||||||
|
Translatable translatable = (Translatable) exception;
|
||||||
|
this.translatableMessage = translatable.getTranslatableMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.translatableMessage = new TranslatableMessage(this.message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,4 +240,16 @@ public class APIError {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a translatable message describing the error that occurred. If no
|
||||||
|
* translatable message is associated with the error, this will be null.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A translatable message describing the error that occurred, or null
|
||||||
|
* if there is no such message defined.
|
||||||
|
*/
|
||||||
|
public TranslatableMessage getTranslatableMessage() {
|
||||||
|
return translatableMessage;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -19,17 +19,16 @@
|
|||||||
|
|
||||||
package org.apache.guacamole.rest;
|
package org.apache.guacamole.rest;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import org.apache.guacamole.form.Field;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception that will result in the given error error information being
|
* An exception which exposes a given error within the API layer. When thrown
|
||||||
* returned from the API layer. All error messages have the same format which
|
* within the context of the REST API, an appropriate HTTP status code will be
|
||||||
* is defined by APIError.
|
* set for the failing response, and the details of the error will be exposed in
|
||||||
|
* the body of the response as an APIError structure.
|
||||||
*
|
*
|
||||||
* @author James Muehlner
|
* @author James Muehlner
|
||||||
* @author Michael Jumper
|
* @author Michael Jumper
|
||||||
@@ -37,88 +36,21 @@ import org.apache.guacamole.protocol.GuacamoleStatus;
|
|||||||
public class APIException extends WebApplicationException {
|
public class APIException extends WebApplicationException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new APIException with the given error. All information
|
* Construct a new APIException based on the given GuacamoleException and
|
||||||
* associated with this new exception will be extracted from the given
|
* HTTP status. The details of the GuacamoleException relevant to the REST
|
||||||
* APIError.
|
* API will be exposed via an APIError.
|
||||||
*
|
*
|
||||||
* @param error
|
* @param status
|
||||||
* The error that occurred.
|
* The HTTP status which corresponds to the GuacamoleException.
|
||||||
|
*
|
||||||
|
* @param exception
|
||||||
|
* The GuacamoleException that occurred.
|
||||||
*/
|
*/
|
||||||
public APIException(APIError error) {
|
public APIException(Response.Status status, GuacamoleException exception) {
|
||||||
super(Response.status(error.getType().getStatus())
|
super(Response.status(status)
|
||||||
.type(MediaType.APPLICATION_JSON)
|
.type(MediaType.APPLICATION_JSON)
|
||||||
.entity(error)
|
.entity(new APIError(exception))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new APIException with the given type and message. The
|
|
||||||
* corresponding APIError will be created from the provided information.
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* The type of error that occurred.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* A human-readable message describing the error.
|
|
||||||
*/
|
|
||||||
public APIException(APIError.Type type, String message) {
|
|
||||||
this(new APIError(type, message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new APIException which represents an error that occurred within
|
|
||||||
* an intercepted Guacamole stream. The nature of that error will be
|
|
||||||
* described by a given status code, which should be the status code
|
|
||||||
* provided by the "ack" instruction that reported the error.
|
|
||||||
*
|
|
||||||
* @param status
|
|
||||||
* The Guacamole protocol status code describing the error that
|
|
||||||
* occurred within the intercepted stream.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* An arbitrary human-readable message describing the error that
|
|
||||||
* occurred.
|
|
||||||
*/
|
|
||||||
public APIException(int status, String message) {
|
|
||||||
this(new APIError(status, message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new APIException which represents an error that occurred within
|
|
||||||
* an intercepted Guacamole stream. The nature of that error will be
|
|
||||||
* described by a given Guacamole protocol status, which should be the
|
|
||||||
* status associated with the code provided by the "ack" instruction that
|
|
||||||
* reported the error.
|
|
||||||
*
|
|
||||||
* @param status
|
|
||||||
* The Guacamole protocol status describing the error that occurred
|
|
||||||
* within the intercepted stream.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* An arbitrary human-readable message describing the error that
|
|
||||||
* occurred.
|
|
||||||
*/
|
|
||||||
public APIException(GuacamoleStatus status, String message) {
|
|
||||||
this(status.getGuacamoleStatusCode(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new APIException with the given type, message, and parameter
|
|
||||||
* information. The corresponding APIError will be created from the
|
|
||||||
* provided information.
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* The type of error that occurred.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* A human-readable message describing the error.
|
|
||||||
*
|
|
||||||
* @param expected
|
|
||||||
* All parameters expected in the original request, or now required as
|
|
||||||
* a result of the original request, as a collection of fields.
|
|
||||||
*/
|
|
||||||
public APIException(APIError.Type type, String message, Collection<Field> expected) {
|
|
||||||
this(new APIError(type, message, expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,8 @@ import java.lang.annotation.Annotation;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import org.aopalliance.intercept.MethodInterceptor;
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.apache.guacamole.GuacamoleClientException;
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
@@ -31,10 +33,7 @@ import org.apache.guacamole.GuacamoleException;
|
|||||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||||
import org.apache.guacamole.GuacamoleSecurityException;
|
import org.apache.guacamole.GuacamoleSecurityException;
|
||||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
|
||||||
import org.apache.guacamole.rest.auth.AuthenticationService;
|
import org.apache.guacamole.rest.auth.AuthenticationService;
|
||||||
import org.apache.guacamole.tunnel.GuacamoleStreamException;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -148,7 +147,7 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
public Object invoke(MethodInvocation invocation) throws WebApplicationException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -172,108 +171,27 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rethrow unchecked exceptions such that they are properly wrapped
|
|
||||||
catch (RuntimeException e) {
|
|
||||||
throw new GuacamoleException(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// Translate GuacamoleException subclasses to HTTP error codes
|
||||||
|
|
||||||
// Additional credentials are needed
|
|
||||||
catch (GuacamoleInsufficientCredentialsException e) {
|
|
||||||
|
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Permission denied.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
APIError.Type.INSUFFICIENT_CREDENTIALS,
|
|
||||||
message,
|
|
||||||
e.getCredentialsInfo().getFields()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The provided credentials are wrong
|
|
||||||
catch (GuacamoleInvalidCredentialsException e) {
|
|
||||||
|
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Permission denied.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
APIError.Type.INVALID_CREDENTIALS,
|
|
||||||
message,
|
|
||||||
e.getCredentialsInfo().getFields()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic permission denied
|
|
||||||
catch (GuacamoleSecurityException e) {
|
catch (GuacamoleSecurityException e) {
|
||||||
|
throw new APIException(Response.Status.FORBIDDEN, e);
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Permission denied.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
APIError.Type.PERMISSION_DENIED,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrary resource not found
|
|
||||||
catch (GuacamoleResourceNotFoundException e) {
|
catch (GuacamoleResourceNotFoundException e) {
|
||||||
|
throw new APIException(Response.Status.NOT_FOUND, e);
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Not found.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
APIError.Type.NOT_FOUND,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrary bad requests
|
|
||||||
catch (GuacamoleClientException e) {
|
catch (GuacamoleClientException e) {
|
||||||
|
throw new APIException(Response.Status.BAD_REQUEST, e);
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Invalid request.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
APIError.Type.BAD_REQUEST,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors from intercepted streams
|
|
||||||
catch (GuacamoleStreamException e) {
|
|
||||||
|
|
||||||
// Generate default message
|
|
||||||
String message = e.getMessage();
|
|
||||||
if (message == null)
|
|
||||||
message = "Error reported by stream.";
|
|
||||||
|
|
||||||
throw new APIException(
|
|
||||||
e.getStatus(),
|
|
||||||
message
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other errors
|
|
||||||
catch (GuacamoleException e) {
|
catch (GuacamoleException e) {
|
||||||
|
throw new APIException(Response.Status.INTERNAL_SERVER_ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
// Log all reasonable details of exception
|
// Rethrow unchecked exceptions such that they are properly wrapped
|
||||||
String message = e.getMessage();
|
catch (Throwable t) {
|
||||||
|
|
||||||
|
// Log all reasonable details of error
|
||||||
|
String message = t.getMessage();
|
||||||
if (message != null)
|
if (message != null)
|
||||||
logger.error("Unexpected internal error: {}", message);
|
logger.error("Unexpected internal error: {}", message);
|
||||||
else
|
else
|
||||||
@@ -282,12 +200,10 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
|||||||
+ "details.");
|
+ "details.");
|
||||||
|
|
||||||
// Ensure internal errors are fully logged at the debug level
|
// Ensure internal errors are fully logged at the debug level
|
||||||
logger.debug("Unexpected exception in REST endpoint.", e);
|
logger.debug("Unexpected error in REST endpoint.", t);
|
||||||
|
|
||||||
throw new APIException(
|
throw new APIException(Response.Status.INTERNAL_SERVER_ERROR,
|
||||||
APIError.Type.INTERNAL_ERROR,
|
new GuacamoleException("Unexpected internal error.", t));
|
||||||
"Unexpected server error."
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,8 +19,9 @@
|
|||||||
|
|
||||||
package org.apache.guacamole.rest.history;
|
package org.apache.guacamole.rest.history;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
import org.apache.guacamole.net.auth.ConnectionRecordSet;
|
import org.apache.guacamole.net.auth.ConnectionRecordSet;
|
||||||
import org.apache.guacamole.rest.APIError;
|
|
||||||
import org.apache.guacamole.rest.APIException;
|
import org.apache.guacamole.rest.APIException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,8 +112,8 @@ public class APIConnectionRecordSortPredicate {
|
|||||||
// Bail out if sort property is not valid
|
// Bail out if sort property is not valid
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
throw new APIException(
|
throw new APIException(
|
||||||
APIError.Type.BAD_REQUEST,
|
Response.Status.BAD_REQUEST,
|
||||||
String.format("Invalid sort property: \"%s\"", value)
|
new GuacamoleClientException(String.format("Invalid sort property: \"%s\"", value))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -578,7 +578,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
|| connectionState === ManagedClientState.ConnectionState.WAITING) {
|
|| connectionState === ManagedClientState.ConnectionState.WAITING) {
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
title: "CLIENT.DIALOG_HEADER_CONNECTING",
|
title: "CLIENT.DIALOG_HEADER_CONNECTING",
|
||||||
text: "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase()
|
text: {
|
||||||
|
key : "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +597,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
notifyConnectionClosed({
|
notifyConnectionClosed({
|
||||||
className : "error",
|
className : "error",
|
||||||
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
||||||
text : "CLIENT.ERROR_CLIENT_" + errorName,
|
text : {
|
||||||
|
key : "CLIENT.ERROR_CLIENT_" + errorName
|
||||||
|
},
|
||||||
countdown : countdown,
|
countdown : countdown,
|
||||||
actions : actions
|
actions : actions
|
||||||
});
|
});
|
||||||
@@ -615,7 +619,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
notifyConnectionClosed({
|
notifyConnectionClosed({
|
||||||
className : "error",
|
className : "error",
|
||||||
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
||||||
text : "CLIENT.ERROR_TUNNEL_" + errorName,
|
text : {
|
||||||
|
key : "CLIENT.ERROR_TUNNEL_" + errorName
|
||||||
|
},
|
||||||
countdown : countdown,
|
countdown : countdown,
|
||||||
actions : actions
|
actions : actions
|
||||||
});
|
});
|
||||||
@@ -626,7 +632,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
else if (connectionState === ManagedClientState.ConnectionState.DISCONNECTED) {
|
else if (connectionState === ManagedClientState.ConnectionState.DISCONNECTED) {
|
||||||
notifyConnectionClosed({
|
notifyConnectionClosed({
|
||||||
title : "CLIENT.DIALOG_HEADER_DISCONNECTED",
|
title : "CLIENT.DIALOG_HEADER_DISCONNECTED",
|
||||||
text : "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase(),
|
text : {
|
||||||
|
key : "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase()
|
||||||
|
},
|
||||||
actions : actions
|
actions : actions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
|||||||
* The message to display to the user as instructions for the login
|
* The message to display to the user as instructions for the login
|
||||||
* process.
|
* process.
|
||||||
*
|
*
|
||||||
* @type String
|
* @type TranslatableMessage
|
||||||
*/
|
*/
|
||||||
$scope.loginHelpText = null;
|
$scope.loginHelpText = null;
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
|
|||||||
$scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, error) {
|
$scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, error) {
|
||||||
$scope.page.title = 'APP.NAME';
|
$scope.page.title = 'APP.NAME';
|
||||||
$scope.page.bodyClassName = '';
|
$scope.page.bodyClassName = '';
|
||||||
$scope.loginHelpText = error.message;
|
$scope.loginHelpText = error.translatableMessage;
|
||||||
$scope.acceptedCredentials = parameters;
|
$scope.acceptedCredentials = parameters;
|
||||||
$scope.expectedCredentials = error.expected;
|
$scope.expectedCredentials = error.expected;
|
||||||
});
|
});
|
||||||
|
@@ -36,7 +36,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() {
|
|||||||
* An optional instructional message to display within the login
|
* An optional instructional message to display within the login
|
||||||
* dialog.
|
* dialog.
|
||||||
*
|
*
|
||||||
* @type String
|
* @type TranslatableMessage
|
||||||
*/
|
*/
|
||||||
helpText : '=',
|
helpText : '=',
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ angular.module('login').directive('guacLogin', [function guacLogin() {
|
|||||||
/**
|
/**
|
||||||
* A description of the error that occurred during login, if any.
|
* A description of the error that occurred during login, if any.
|
||||||
*
|
*
|
||||||
* @type String
|
* @type TranslatableMessage
|
||||||
*/
|
*/
|
||||||
$scope.loginError = null;
|
$scope.loginError = null;
|
||||||
|
|
||||||
@@ -160,11 +160,13 @@ angular.module('login').directive('guacLogin', [function guacLogin() {
|
|||||||
|
|
||||||
// Flag generic error for invalid login
|
// Flag generic error for invalid login
|
||||||
if (error.type === Error.Type.INVALID_CREDENTIALS)
|
if (error.type === Error.Type.INVALID_CREDENTIALS)
|
||||||
$scope.loginError = 'LOGIN.ERROR_INVALID_LOGIN';
|
$scope.loginError = {
|
||||||
|
'key' : 'LOGIN.ERROR_INVALID_LOGIN'
|
||||||
|
};
|
||||||
|
|
||||||
// Display error if anything else goes wrong
|
// Display error if anything else goes wrong
|
||||||
else
|
else
|
||||||
$scope.loginError = error.message;
|
$scope.loginError = error.translatableMessage;
|
||||||
|
|
||||||
// Clear all visible password fields
|
// Clear all visible password fields
|
||||||
angular.forEach($scope.remainingFields, function clearEnteredValueIfPassword(field) {
|
angular.forEach($scope.remainingFields, function clearEnteredValueIfPassword(field) {
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
<div class="login-ui" ng-class="{error: loginError, continuation: isContinuation(), initial: !isContinuation()}" >
|
<div class="login-ui" ng-class="{error: loginError, continuation: isContinuation(), initial: !isContinuation()}" >
|
||||||
|
|
||||||
<!-- Login error message -->
|
<!-- Login error message -->
|
||||||
<p class="login-error">{{loginError | translate}}</p>
|
<p class="login-error" translate="{{loginError.key}}"
|
||||||
|
translate-values="{{loginError.variables}}"></p>
|
||||||
|
|
||||||
<div class="login-dialog-middle">
|
<div class="login-dialog-middle">
|
||||||
|
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Login message/instructions -->
|
<!-- Login message/instructions -->
|
||||||
<p ng-show="helpText">{{helpText | translate}}</p>
|
<p ng-show="helpText" translate="{{helpText.key}}"
|
||||||
|
translate-values="{{helpText.variables}}"></p>
|
||||||
|
|
||||||
<!-- Login fields -->
|
<!-- Login fields -->
|
||||||
<div class="login-fields">
|
<div class="login-fields">
|
||||||
|
@@ -380,7 +380,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -430,7 +430,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -446,7 +446,9 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
|||||||
// Confirm deletion request
|
// Confirm deletion request
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_CONFIRM_DELETE',
|
'title' : 'MANAGE_CONNECTION.DIALOG_HEADER_CONFIRM_DELETE',
|
||||||
'text' : 'MANAGE_CONNECTION.TEXT_CONFIRM_DELETE',
|
'text' : {
|
||||||
|
key : 'MANAGE_CONNECTION.TEXT_CONFIRM_DELETE'
|
||||||
|
},
|
||||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -220,7 +220,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -270,7 +270,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -286,7 +286,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
|||||||
// Confirm deletion request
|
// Confirm deletion request
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_CONFIRM_DELETE',
|
'title' : 'MANAGE_CONNECTION_GROUP.DIALOG_HEADER_CONFIRM_DELETE',
|
||||||
'text' : 'MANAGE_CONNECTION_GROUP.TEXT_CONFIRM_DELETE',
|
'text' : {
|
||||||
|
key : 'MANAGE_CONNECTION_GROUP.TEXT_CONFIRM_DELETE'
|
||||||
|
},
|
||||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -341,7 +341,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -379,7 +379,7 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -395,7 +395,9 @@ angular.module('manage').controller('manageSharingProfileController', ['$scope',
|
|||||||
// Confirm deletion request
|
// Confirm deletion request
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_CONFIRM_DELETE',
|
'title' : 'MANAGE_SHARING_PROFILE.DIALOG_HEADER_CONFIRM_DELETE',
|
||||||
'text' : 'MANAGE_SHARING_PROFILE.TEXT_CONFIRM_DELETE',
|
'text' : {
|
||||||
|
'key' : 'MANAGE_SHARING_PROFILE.TEXT_CONFIRM_DELETE'
|
||||||
|
},
|
||||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1012,7 +1012,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
||||||
'text' : 'MANAGE_USER.ERROR_PASSWORD_MISMATCH',
|
'text' : {
|
||||||
|
key : 'MANAGE_USER.ERROR_PASSWORD_MISMATCH'
|
||||||
|
},
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -1055,7 +1057,8 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
|
'values' : error.translationValues,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1067,7 +1070,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1117,7 +1120,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
'title' : 'MANAGE_USER.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1133,7 +1136,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
|||||||
// Confirm deletion request
|
// Confirm deletion request
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'title' : 'MANAGE_USER.DIALOG_HEADER_CONFIRM_DELETE',
|
'title' : 'MANAGE_USER.DIALOG_HEADER_CONFIRM_DELETE',
|
||||||
'text' : 'MANAGE_USER.TEXT_CONFIRM_DELETE',
|
'text' : {
|
||||||
|
key : 'MANAGE_USER.TEXT_CONFIRM_DELETE'
|
||||||
|
},
|
||||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -60,7 +60,9 @@ angular.module('notification').factory('guacNotification', ['$injector',
|
|||||||
* // To show a status message with actions
|
* // To show a status message with actions
|
||||||
* guacNotification.showStatus({
|
* guacNotification.showStatus({
|
||||||
* 'title' : 'Disconnected',
|
* 'title' : 'Disconnected',
|
||||||
* 'text' : 'You have been disconnected!',
|
* 'text' : {
|
||||||
|
* 'key' : 'NAMESPACE.SOME_TRANSLATION_KEY'
|
||||||
|
* },
|
||||||
* 'actions' : {
|
* 'actions' : {
|
||||||
* 'name' : 'reconnect',
|
* 'name' : 'reconnect',
|
||||||
* 'callback' : function () {
|
* 'callback' : function () {
|
||||||
|
@@ -8,7 +8,9 @@
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
|
|
||||||
<!-- Notification text -->
|
<!-- Notification text -->
|
||||||
<p ng-show="notification.text" class="text">{{notification.text | translate}}</p>
|
<p ng-show="notification.text" class="text"
|
||||||
|
translate="{{notification.text.key}}"
|
||||||
|
translate-values="{{notification.text.variables}}"></p>
|
||||||
|
|
||||||
<!-- Current progress -->
|
<!-- Current progress -->
|
||||||
<div class="progress" ng-show="notification.progress"><div class="bar" ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}"></div><div
|
<div class="progress" ng-show="notification.progress"><div class="bar" ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}"></div><div
|
||||||
|
@@ -53,7 +53,7 @@ angular.module('notification').factory('Notification', [function defineNotificat
|
|||||||
/**
|
/**
|
||||||
* The body text of the notification.
|
* The body text of the notification.
|
||||||
*
|
*
|
||||||
* @type String
|
* @type TranslatableMessage
|
||||||
*/
|
*/
|
||||||
this.text = template.text;
|
this.text = template.text;
|
||||||
|
|
||||||
|
@@ -42,6 +42,15 @@ angular.module('rest').factory('Error', [function defineError() {
|
|||||||
*/
|
*/
|
||||||
this.message = template.message;
|
this.message = template.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message which can be translated using the translation service,
|
||||||
|
* consisting of a translation key and optional set of substitution
|
||||||
|
* variables.
|
||||||
|
*
|
||||||
|
* @type TranslatableMessage
|
||||||
|
*/
|
||||||
|
this.translatableMessage = template.translatableMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Guacamole protocol status code associated with the error that
|
* The Guacamole protocol status code associated with the error that
|
||||||
* occurred. This is only valid for errors of type STREAM_ERROR.
|
* occurred. This is only valid for errors of type STREAM_ERROR.
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service which defines the TranslatableMessage class.
|
||||||
|
*/
|
||||||
|
angular.module('rest').factory('TranslatableMessage', [function defineTranslatableMessage() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object returned by REST API calls when representing a message which
|
||||||
|
* can be translated using the translation service, providing a translation
|
||||||
|
* key and optional set of values to be substituted into the translation
|
||||||
|
* string associated with that key.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {TranslatableMessage|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* TranslatableMessage.
|
||||||
|
*/
|
||||||
|
var TranslatableMessage = function TranslatableMessage(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key associated with the translation string that used when
|
||||||
|
* displaying this message.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
this.key = template.key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object which should be passed through to the translation service
|
||||||
|
* for the sake of variable substitution. Each property of the provided
|
||||||
|
* object will be substituted for the variable of the same name within
|
||||||
|
* the translation string.
|
||||||
|
*
|
||||||
|
* @type Object
|
||||||
|
*/
|
||||||
|
this.variables = template.variables;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return TranslatableMessage;
|
||||||
|
|
||||||
|
}]);
|
@@ -127,7 +127,9 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
className : 'error',
|
className : 'error',
|
||||||
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
||||||
text : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_MISMATCH',
|
text : {
|
||||||
|
key : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_MISMATCH'
|
||||||
|
},
|
||||||
actions : [ ACKNOWLEDGE_ACTION ]
|
actions : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +140,9 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
className : 'error',
|
className : 'error',
|
||||||
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
||||||
text : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_BLANK',
|
text : {
|
||||||
|
key : 'SETTINGS_PREFERENCES.ERROR_PASSWORD_BLANK'
|
||||||
|
},
|
||||||
actions : [ ACKNOWLEDGE_ACTION ]
|
actions : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -155,7 +159,9 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
|||||||
|
|
||||||
// Indicate that the password has been changed
|
// Indicate that the password has been changed
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
text : 'SETTINGS_PREFERENCES.INFO_PASSWORD_CHANGED',
|
text : {
|
||||||
|
key : 'SETTINGS_PREFERENCES.INFO_PASSWORD_CHANGED'
|
||||||
|
},
|
||||||
actions : [ ACKNOWLEDGE_ACTION ]
|
actions : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -165,7 +171,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
className : 'error',
|
className : 'error',
|
||||||
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
title : 'SETTINGS_PREFERENCES.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
text : error.translatableMessage,
|
||||||
actions : [ ACKNOWLEDGE_ACTION ]
|
actions : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -334,7 +334,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
|||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'className' : 'error',
|
'className' : 'error',
|
||||||
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_ERROR',
|
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_ERROR',
|
||||||
'text' : error.message,
|
'text' : error.translatableMessage,
|
||||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -349,7 +349,9 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
|||||||
// Confirm deletion request
|
// Confirm deletion request
|
||||||
guacNotification.showStatus({
|
guacNotification.showStatus({
|
||||||
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_CONFIRM_DELETE',
|
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_CONFIRM_DELETE',
|
||||||
'text' : 'SETTINGS_SESSIONS.TEXT_CONFIRM_DELETE',
|
'text' : {
|
||||||
|
'key' : 'SETTINGS_SESSIONS.TEXT_CONFIRM_DELETE'
|
||||||
|
},
|
||||||
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
'actions' : [ DELETE_ACTION, CANCEL_ACTION]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user