From 0df72ec6d40bd91f612521e18191e9af5b83642b Mon Sep 17 00:00:00 2001
From: John B
Date: Fri, 17 Dec 2021 10:44:18 -0500
Subject: [PATCH 1/7] #1191 - Garden configuration validation
---
src/app/beer_garden/api/http/client.py | 5 +
.../api/http/handlers/v1/garden.py | 15 +-
src/app/beer_garden/db/mongo/api.py | 22 +-
src/app/beer_garden/db/mongo/models.py | 14 +-
.../beer_garden/db/schemas/garden_schema.py | 110 +++++++
src/app/beer_garden/garden.py | 49 ++-
src/app/test/auth_test.py | 6 +-
.../test/db/mongo/models/garden_model_test.py | 310 ++++++++++++++++++
.../test/db/mongo/{ => models}/models_test.py | 137 --------
src/app/test/garden_test.py | 20 +-
10 files changed, 524 insertions(+), 164 deletions(-)
create mode 100644 src/app/beer_garden/db/schemas/garden_schema.py
create mode 100644 src/app/test/db/mongo/models/garden_model_test.py
rename src/app/test/db/mongo/{ => models}/models_test.py (85%)
diff --git a/src/app/beer_garden/api/http/client.py b/src/app/beer_garden/api/http/client.py
index 95176edfa..26aa431b5 100644
--- a/src/app/beer_garden/api/http/client.py
+++ b/src/app/beer_garden/api/http/client.py
@@ -5,10 +5,12 @@
import six
from brewtils.models import BaseModel
+from brewtils.models import Garden as BrewtilsGarden
from brewtils.schema_parser import SchemaParser
import beer_garden.api
import beer_garden.router
+from beer_garden.db.schemas.garden_schema import GardenSchema
class SerializeHelper(object):
@@ -31,6 +33,9 @@ async def __call__(self, *args, serialize_kwargs=None, **kwargs):
if self.json_dump(result):
return json.dumps(result) if serialize_kwargs["to_string"] else result
+ if isinstance(result, BrewtilsGarden):
+ return GardenSchema(strict=True).dumps(result).data
+
return SchemaParser.serialize(result, **(serialize_kwargs or {}))
@staticmethod
diff --git a/src/app/beer_garden/api/http/handlers/v1/garden.py b/src/app/beer_garden/api/http/handlers/v1/garden.py
index 0c94a7459..c08e0145f 100644
--- a/src/app/beer_garden/api/http/handlers/v1/garden.py
+++ b/src/app/beer_garden/api/http/handlers/v1/garden.py
@@ -2,11 +2,12 @@
from brewtils.errors import ModelValidationError
from brewtils.models import Operation
from brewtils.schema_parser import SchemaParser
+from mongoengine.queryset.queryset import QuerySet
from beer_garden.api.authorization import Permissions
from beer_garden.api.http.handlers import AuthorizationHandler
-from beer_garden.db.mongo.api import MongoParser
from beer_garden.db.mongo.models import Garden
+from beer_garden.db.schemas.garden_schema import GardenSchema
from beer_garden.garden import local_garden
GARDEN_CREATE = Permissions.GARDEN_CREATE.value
@@ -40,7 +41,7 @@ async def get(self, garden_name):
"""
garden = self.get_or_raise(Garden, GARDEN_READ, name=garden_name)
- response = MongoParser.serialize(garden)
+ response = GardenSchema(strict=True).dumps(garden).data
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(response)
@@ -138,10 +139,12 @@ async def patch(self, garden_name):
)
)
elif operation == "config":
+ garden_to_update = GardenSchema(strict=True).load(op.value).data
+ garden_to_update.id = garden.id
response = await self.client(
Operation(
operation_type="GARDEN_UPDATE_CONFIG",
- args=[SchemaParser.parse_garden(op.value, from_string=False)],
+ args=[garden_to_update],
)
)
elif operation == "sync":
@@ -178,9 +181,9 @@ async def get(self):
tags:
- Garden
"""
- permitted_gardens = self.permissioned_queryset(Garden, GARDEN_READ)
+ permitted_gardens: QuerySet = self.permissioned_queryset(Garden, GARDEN_READ)
- response = MongoParser.serialize(permitted_gardens, to_string=True)
+ response = GardenSchema(strict=True, many=True).dumps(permitted_gardens).data
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(response)
@@ -207,7 +210,7 @@ async def post(self):
tags:
- Garden
"""
- garden = SchemaParser.parse_garden(self.request.decoded_body, from_string=True)
+ garden = GardenSchema(strict=True).loads(self.request.decoded_body).data
self.verify_user_permission_for_object(GARDEN_CREATE, garden)
diff --git a/src/app/beer_garden/db/mongo/api.py b/src/app/beer_garden/db/mongo/api.py
index 96eb778f0..97fb0aec0 100644
--- a/src/app/beer_garden/db/mongo/api.py
+++ b/src/app/beer_garden/db/mongo/api.py
@@ -75,7 +75,13 @@ def from_brewtils(obj: ModelItem) -> MongoModel:
The Mongo model item
"""
- model_dict = SchemaParser.serialize(obj, to_string=False)
+ if isinstance(obj, brewtils.models.Garden):
+ # first step in decoupling from Brewtils
+ from beer_garden.db.schemas.garden_schema import GardenSchema
+
+ model_dict = GardenSchema(strict=True).dump(obj).data
+ else:
+ model_dict = SchemaParser.serialize(obj, to_string=False)
mongo_obj = MongoParser.parse(model_dict, type(obj), from_string=False)
return mongo_obj
@@ -108,8 +114,18 @@ def to_brewtils(
if getattr(obj, "pre_serialize", None):
obj.pre_serialize()
- serialized = MongoParser.serialize(obj, to_string=True)
- parsed = SchemaParser.parse(serialized, model_class, from_string=True, many=many)
+ if model_class == brewtils.models.Garden:
+ # first step in decoupling from Brewtils
+ from beer_garden.db.schemas.garden_schema import GardenSchema
+
+ schema = GardenSchema(strict=True)
+ serialized = schema.dumps(obj, many=many).data
+ parsed = schema.loads(serialized, many=many).data
+ else:
+ serialized = MongoParser.serialize(obj, to_string=True)
+ parsed = SchemaParser.parse(
+ serialized, model_class, from_string=True, many=many
+ )
return parsed
diff --git a/src/app/beer_garden/db/mongo/models.py b/src/app/beer_garden/db/mongo/models.py
index 5346d2fa1..b6f6fc5a9 100644
--- a/src/app/beer_garden/db/mongo/models.py
+++ b/src/app/beer_garden/db/mongo/models.py
@@ -5,8 +5,11 @@
import pytz
import six
+from marshmallow import ValidationError as MarshmallowValidationError
from passlib.apps import custom_app_context
+from beer_garden.db.schemas.garden_schema import GardenConnectionsParamsSchema
+
try:
from lark import ParseError
from lark.exceptions import LarkError
@@ -49,6 +52,7 @@
UUIDField,
ValidationError,
)
+from mongoengine.errors import ValidationError as MongoengineValidationError
from beer_garden import config
from beer_garden.db.mongo.querysets import FileFieldHandlingQuerySet
@@ -781,6 +785,14 @@ def clean(self):
)
+def validate_garden_connection_params(dict_field):
+ """Use the marshmallow schema to validate Garden connection parameters."""
+ try:
+ GardenConnectionsParamsSchema(strict=True).validate(dict(dict_field))
+ except MarshmallowValidationError as mmve:
+ raise MongoengineValidationError(mmve.messages)
+
+
class Garden(MongoModel, Document):
brewtils_model = brewtils.models.Garden
@@ -789,7 +801,7 @@ class Garden(MongoModel, Document):
status_info = EmbeddedDocumentField("StatusInfo", default=StatusInfo())
namespaces = ListField()
connection_type = StringField()
- connection_params = DictField()
+ connection_params = DictField(validation=validate_garden_connection_params)
systems = ListField(ReferenceField(System, reverse_delete_rule=PULL))
meta = {
diff --git a/src/app/beer_garden/db/schemas/garden_schema.py b/src/app/beer_garden/db/schemas/garden_schema.py
new file mode 100644
index 000000000..4c3d509b3
--- /dev/null
+++ b/src/app/beer_garden/db/schemas/garden_schema.py
@@ -0,0 +1,110 @@
+import logging
+
+from brewtils.models import Garden as BrewtilsGarden
+from brewtils.schemas import StatusInfoSchema # noqa # until we can fully decouple
+from brewtils.schemas import SystemSchema # noqa # until we can fully decouple
+from marshmallow import Schema, ValidationError, fields
+from marshmallow.decorators import post_load, pre_load, validates_schema
+from mongoengine.queryset.queryset import QuerySet
+
+logger = logging.getLogger(__name__)
+
+
+class GardenBaseSchema(Schema):
+ """Class to give Marshmallow Schemas the desired behavior of throwing
+ exceptions on errors when marshalling/unmarshalling. Otherwise, each line of code
+ utilizing these would need to pull apart MarshalResult objects in order to return
+ a meaningful error."""
+
+ @validates_schema(skip_on_field_errors=False, pass_original=True)
+ def validate_all_keys(self, post_load_data, original_data, **kwargs):
+ # do not allow extraneous keys when operating on a dictionary
+ if isinstance(original_data, dict):
+ extra_args = original_data.keys() - post_load_data.keys()
+
+ if len(extra_args) > 0:
+ formatted_good_keys = ", ".join(
+ map(lambda x: "'" + str(x) + "'", self.fields.keys())
+ )
+ formatted_bad_keys = ", ".join(
+ map(lambda x: "'" + str(x) + "'", extra_args)
+ )
+ raise ValidationError(
+ f"Only {formatted_good_keys} allowed as keys; "
+ f"these are not allowed: {formatted_bad_keys}"
+ )
+
+
+def _port_validator(value):
+ return 0 < value < 65535
+
+
+class HttpConnectionParamsSchema(GardenBaseSchema):
+ host = fields.String(required=True)
+ port = fields.Integer(
+ required=True,
+ validate=_port_validator,
+ error_messages={
+ **fields.Field.default_error_messages,
+ **{"validator_failed": "Value out of range for ports"},
+ },
+ )
+ url_prefix = fields.String(required=True, dump_default="/", load_default="/")
+ ca_cert = fields.String(required=False, allow_none=True)
+ ca_verify = fields.Boolean(required=True)
+ client_cert = fields.String(required=False, allow_none=True)
+ client_key = fields.String(required=False, allow_none=True)
+ ssl = fields.Boolean(required=True)
+
+
+class StompSSLParamsSchema(GardenBaseSchema):
+ use_ssl = fields.Boolean(required=True)
+
+
+class StompHeaderSchema(GardenBaseSchema):
+ key = fields.String(required=True)
+ value = fields.String(required=True)
+
+
+class StompConnectionParamsSchema(GardenBaseSchema):
+ ssl = fields.Nested("StompSSLParamsSchema", required=True)
+ headers = fields.List(fields.Nested("StompHeaderSchema"), required=False)
+ host = fields.String(required=True)
+ port = fields.Integer(
+ required=True,
+ validate=_port_validator,
+ error_messages={
+ **fields.Field.default_error_messages,
+ **{"validator_failed": "Value out of range for ports"},
+ },
+ )
+ send_destination = fields.String(required=False, allow_none=True)
+ subscribe_destination = fields.String(required=False, allow_none=True)
+ username = fields.String(required=False, allow_none=True)
+ password = fields.String(required=False, allow_none=True)
+
+
+class GardenConnectionsParamsSchema(GardenBaseSchema):
+ http = fields.Nested("HttpConnectionParamsSchema", allow_none=True)
+ stomp = fields.Nested("StompConnectionParamsSchema", allow_none=True)
+
+
+class GardenSchema(GardenBaseSchema):
+ id = fields.Str(allow_none=True)
+ # TODO the name field must be allowed to be blank for child garden registration
+ name = fields.Str(allow_none=False)
+ status = fields.Str(allow_none=True)
+ status_info = fields.Nested(StatusInfoSchema, allow_none=True)
+ connection_type = fields.Str(allow_none=False)
+ connection_params = fields.Nested(
+ "GardenConnectionsParamsSchema",
+ allow_none=True,
+ dump_default={},
+ load_default={},
+ )
+ namespaces = fields.List(fields.Str(), allow_none=True)
+ systems = fields.Nested(SystemSchema, many=True, allow_none=True)
+
+ @post_load
+ def make_object(self, data):
+ return BrewtilsGarden(**data)
diff --git a/src/app/beer_garden/garden.py b/src/app/beer_garden/garden.py
index 2dd64f098..3bdf2a3af 100644
--- a/src/app/beer_garden/garden.py
+++ b/src/app/beer_garden/garden.py
@@ -168,6 +168,49 @@ def remove_garden(garden_name: str) -> None:
return garden
+def get_connection_defaults():
+ # Explicitly load default config options into garden params
+ spec = YapconfSpec(_CONNECTION_SPEC)
+ # bg_host is required to load brewtils garden spec
+ defaults = spec.load_config({"bg_host": ""})
+
+ config_map = {
+ "bg_host": "host",
+ "bg_port": "port",
+ "ssl_enabled": "ssl",
+ "bg_url_prefix": "url_prefix",
+ "ca_cert": "ca_cert",
+ "ca_verify": "ca_verify",
+ "client_cert": "client_cert",
+ }
+
+ # TODO: this is a temporary work-around until Brewtils is configured to provide
+ # sensible defaults
+ sensible_defaults = {
+ "bg_host": "somehostname",
+ "bg_port": 1025,
+ "ssl_enabled": False,
+ "bg_url_prefix": "/",
+ "ca_cert": "none",
+ "ca_verify": False,
+ "client_cert": "none",
+ }
+ # substitute the sensible default only if we're provided `None` or an empty string
+ defaults = {
+ key: (
+ defaults[key]
+ if (
+ (defaults[key] is not None and (isinstance(defaults[key], bool)))
+ or defaults[key]
+ )
+ else sensible_defaults[key]
+ )
+ for key in config_map
+ }
+
+ return defaults
+
+
@publish_event(Events.GARDEN_CREATED)
def create_garden(garden: Garden) -> Garden:
"""Create a new Garden
@@ -179,11 +222,6 @@ def create_garden(garden: Garden) -> Garden:
The created Garden
"""
- # Explicitly load default config options into garden params
- spec = YapconfSpec(_CONNECTION_SPEC)
- # bg_host is required to load brewtils garden spec
- defaults = spec.load_config({"bg_host": ""})
-
config_map = {
"bg_host": "host",
"bg_port": "port",
@@ -193,6 +231,7 @@ def create_garden(garden: Garden) -> Garden:
"ca_verify": "ca_verify",
"client_cert": "client_cert",
}
+ defaults = get_connection_defaults()
if garden.connection_params is None:
garden.connection_params = {}
diff --git a/src/app/test/auth_test.py b/src/app/test/auth_test.py
index f97ca904d..6f7fa9a00 100644
--- a/src/app/test/auth_test.py
+++ b/src/app/test/auth_test.py
@@ -113,7 +113,11 @@ def user_with_role_assignments(
@pytest.fixture
def test_garden(role_assignment_for_garden_scope):
- garden = Garden(**role_assignment_for_garden_scope.domain.identifiers).save()
+ args = {
+ **{"connection_type": "LOCAL"},
+ **role_assignment_for_garden_scope.domain.identifiers,
+ }
+ garden = Garden(**args).save()
yield garden
garden.delete()
diff --git a/src/app/test/db/mongo/models/garden_model_test.py b/src/app/test/db/mongo/models/garden_model_test.py
new file mode 100644
index 000000000..3afcbd7a7
--- /dev/null
+++ b/src/app/test/db/mongo/models/garden_model_test.py
@@ -0,0 +1,310 @@
+# -*- coding: utf-8 -*-
+import copy
+from contextlib import nullcontext as does_not_raise
+
+import pytest
+from mongoengine import NotUniqueError, connect
+from mongoengine.errors import ValidationError
+
+from beer_garden.db.mongo.models import Garden, System
+
+v1_str = "v1"
+v2_str = "v2"
+garden_name = "test_garden"
+
+garbage_headers_extra_key = [
+ {
+ "key": "key_2",
+ "value": "value_2",
+ "extra_key": "value_doesnt_matter",
+ },
+]
+garbage_headers_wrong_key = [
+ {"notakey": "key_1", "value": "value_1"},
+]
+
+
+class TestGarden:
+ @classmethod
+ def setup_class(cls):
+ connect("beer_garden", host="mongomock://localhost")
+ Garden.drop_collection()
+ Garden.ensure_indexes()
+
+ @pytest.fixture()
+ def local_garden(self):
+ garden = Garden(name=garden_name, connection_type="LOCAL").save()
+ yield garden
+ garden.delete()
+
+ @pytest.fixture
+ def child_system(self):
+ return System(name="echoer", namespace="child_garden")
+
+ @pytest.fixture
+ def child_system_v1(self, child_system):
+ system: System = copy.deepcopy(child_system)
+ system.version = v1_str
+ system.save()
+ yield system
+ system.delete()
+
+ @pytest.fixture
+ def child_system_v2(self, child_system):
+ system: System = copy.deepcopy(child_system)
+ system.version = v2_str
+ system.save()
+ yield system
+ system.delete()
+
+ @pytest.fixture
+ def child_system_v1_diff_id(self, child_system):
+ system: System = copy.deepcopy(child_system)
+ system.version = v1_str
+ system.save()
+ yield system
+ system.delete()
+
+ @pytest.fixture
+ def child_garden(self, child_system_v1):
+ garden = Garden(
+ name="child_garden", connection_type="HTTP", systems=[child_system_v1]
+ ).save()
+ yield garden
+ garden.delete()
+
+ def test_garden_names_are_required_to_be_unique(self, local_garden):
+ """Attempting to create a garden that shares a name with an existing garden
+ should raise an exception"""
+ with pytest.raises(NotUniqueError):
+ Garden(name=local_garden.name, connection_type="HTTP").save()
+
+ def test_only_one_local_garden_may_exist(self, local_garden):
+ """Attempting to create more than one garden with connection_type of LOCAL
+ should raise an exception"""
+ with pytest.raises(NotUniqueError):
+ Garden(name=f"not{local_garden.name}", connection_type="LOCAL").save()
+
+ def test_child_garden_system_attrib_update(self, child_garden, child_system_v2):
+ """If the systems of a child garden are updated such that their names,
+ namespaces, or versions are changed, the original systems are removed and
+ replaced with the new systems when the garden is saved."""
+ orig_system_ids = set(
+ map(lambda x: str(getattr(x, "id")), child_garden.systems) # noqa: B009
+ )
+ orig_system_versions = set(
+ map(
+ lambda x: str(getattr(x, "version")), child_garden.systems # noqa: B009
+ )
+ )
+
+ assert v1_str in orig_system_versions and v2_str not in orig_system_versions
+
+ child_garden.systems = [child_system_v2]
+ child_garden.deep_save()
+
+ # we check that the garden written to the DB has the correct systems
+ db_garden = Garden.objects().first()
+
+ new_system_ids = set(
+ map(lambda x: str(getattr(x, "id")), db_garden.systems) # noqa: B009
+ )
+ new_system_versions = set(
+ map(lambda x: str(getattr(x, "version")), db_garden.systems) # noqa: B009
+ )
+
+ assert v1_str not in new_system_versions and v2_str in new_system_versions
+ assert new_system_ids.intersection(orig_system_ids) == set()
+
+ def test_child_garden_system_id_update(self, child_garden, child_system_v1_diff_id):
+ """If the systems of a child garden are updated such that the names, namespaces
+ and versions remain constant, but the IDs are different, the original systms
+ are removed and replaced with the new systems when the garden is saved."""
+ orig_system_ids = set(
+ map(lambda x: str(getattr(x, "id")), child_garden.systems) # noqa: B009
+ )
+ new_system_id = str(child_system_v1_diff_id.id)
+
+ assert new_system_id not in orig_system_ids
+
+ child_garden.systems = [child_system_v1_diff_id]
+ child_garden.deep_save()
+ db_garden = Garden.objects().first()
+
+ new_system_ids = set(
+ map(lambda x: str(getattr(x, "id")), db_garden.systems) # noqa: B009
+ )
+
+ assert new_system_id in new_system_ids
+ assert orig_system_ids.intersection(new_system_ids) == set()
+
+
+class TestGardenConnectionParameters:
+ @classmethod
+ def setup_class(cls):
+ connect("beer_garden", host="mongomock://localhost")
+ Garden.drop_collection()
+ Garden.ensure_indexes()
+
+ @pytest.fixture(autouse=True)
+ def drop(self):
+ Garden.drop_collection()
+
+ @pytest.fixture
+ def bad_conn_params(self):
+ return dict([("nonempty", "dictionaries"), ("should", "fail")])
+
+ @pytest.fixture
+ def http_conn_params(self):
+ return {
+ "http": {
+ "port": 2337,
+ "ssl": True,
+ "url_prefix": "/",
+ "ca_verify": True,
+ "host": "bg-child1",
+ }
+ }
+
+ @pytest.fixture
+ def stomp_conn_params_basic(self):
+ return {
+ "stomp": {
+ "ssl": {"use_ssl": False},
+ "headers": [],
+ "host": "activemq",
+ "port": 61613,
+ "send_destination": "send_destination",
+ "subscribe_destination": "subscribe_destination",
+ "username": "beer_garden",
+ "password": "password",
+ }
+ }
+
+ @pytest.fixture
+ def stomp_conn_params_with_headers(self, stomp_conn_params_basic):
+ stomp_conn_params = copy.deepcopy(stomp_conn_params_basic)
+ headers = [{"key": f"key_{i+1}", "value": f"value_{i+1}"} for i in range(3)]
+ stomp_conn_params["stomp"]["headers"] = headers
+ return stomp_conn_params
+
+ @pytest.fixture
+ def bad_conn_params_with_partial_good(self, http_conn_params, bad_conn_params):
+ return {**http_conn_params, **bad_conn_params}
+
+ @pytest.fixture
+ def bad_conn_params_with_full_good(
+ self, bad_conn_params_with_partial_good, stomp_conn_params_basic
+ ):
+ return {**stomp_conn_params_basic, **bad_conn_params_with_partial_good}
+
+ @pytest.mark.parametrize(
+ "conn_parm",
+ (
+ pytest.lazy_fixture("bad_conn_params"),
+ pytest.lazy_fixture("bad_conn_params_with_partial_good"),
+ pytest.lazy_fixture("bad_conn_params_with_full_good"),
+ ),
+ )
+ def test_local_garden_save_fails_with_nonempty_conn_params(self, conn_parm):
+ with pytest.raises(ValidationError) as excinfo:
+ Garden(
+ name=garden_name,
+ connection_type="LOCAL",
+ connection_params=conn_parm,
+ ).save()
+ assert "not allowed" in str(excinfo.value)
+
+ def test_local_garden_save_succeeds_with_empty_conn_params(self):
+ with does_not_raise():
+ Garden(
+ name=garden_name, connection_type="LOCAL", connection_params={}
+ ).save().delete()
+
+ @pytest.mark.parametrize(
+ "conn_parm",
+ (
+ pytest.lazy_fixture("bad_conn_params"),
+ # pytest.lazy_fixture("bad_conn_params_with_partial_good"),
+ # pytest.lazy_fixture("bad_conn_params_with_full_good"),
+ ),
+ )
+ def test_remote_garden_save_fails_with_bad_conn_params(self, conn_parm):
+ with pytest.raises(ValidationError) as excinfo:
+ Garden(
+ name=garden_name,
+ connection_type="HTTP",
+ connection_params=conn_parm,
+ ).save()
+ assert "not allowed" in str(excinfo.value)
+
+ @pytest.mark.parametrize("required", ("port", "ssl", "ca_verify", "host"))
+ def test_required_http_params_missing_fails(self, http_conn_params, required):
+ conn_param_values = http_conn_params["http"]
+ _ = conn_param_values.pop(required)
+ conn_params = {"http": conn_param_values}
+
+ with pytest.raises(ValidationError) as excinfo:
+ Garden(
+ name=garden_name, connection_type="HTTP", connection_params=conn_params
+ ).save()
+ assert "Missing data" in str(excinfo.value)
+
+ @pytest.mark.parametrize("required", ("ssl", "host", "port"))
+ def test_required_stomp_params_missing_fails(
+ self, stomp_conn_params_basic, required
+ ):
+ conn_param_values = stomp_conn_params_basic["stomp"]
+ _ = conn_param_values.pop(required)
+ conn_params = {"stomp": conn_param_values}
+
+ with pytest.raises(ValidationError) as excinfo:
+ Garden(
+ name=garden_name, connection_type="HTTP", connection_params=conn_params
+ ).save()
+ assert "Missing data" in str(excinfo.value)
+
+ def test_remote_garden_save_succeeds_with_only_good_http_headers(
+ self, http_conn_params
+ ):
+ garden = Garden(
+ name=garden_name,
+ connection_type="HTTP",
+ connection_params=http_conn_params,
+ )
+ with does_not_raise():
+ garden.save()
+ garden.delete()
+
+ def test_remote_garden_save_succeeds_with_only_good_stomp_headers(
+ self, stomp_conn_params_with_headers
+ ):
+ garden = Garden(
+ name=garden_name,
+ connection_type="STOMP",
+ connection_params=stomp_conn_params_with_headers,
+ )
+ with does_not_raise():
+ garden.save()
+ garden.delete()
+
+ @pytest.mark.parametrize(
+ "bad_headers",
+ (
+ garbage_headers_extra_key,
+ # garbage_headers_wrong_key,
+ ),
+ )
+ def test_remote_garden_save_fails_with_garbage_stomp_headers(
+ self, stomp_conn_params_basic, bad_headers
+ ):
+ test_params = stomp_conn_params_basic["stomp"]
+ test_params["headers"] = bad_headers
+ connection_params = {"stomp": test_params}
+
+ with pytest.raises(ValidationError) as exc:
+ Garden(
+ name=garden_name,
+ connection_type="STOMP",
+ connection_params=connection_params,
+ ).save()
diff --git a/src/app/test/db/mongo/models_test.py b/src/app/test/db/mongo/models/models_test.py
similarity index 85%
rename from src/app/test/db/mongo/models_test.py
rename to src/app/test/db/mongo/models/models_test.py
index 52e04df8e..0e3f04a76 100644
--- a/src/app/test/db/mongo/models_test.py
+++ b/src/app/test/db/mongo/models/models_test.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-import copy
from datetime import datetime, timedelta
from uuid import uuid4
@@ -18,7 +17,6 @@
Command,
CommandPublishingBlockList,
DateTrigger,
- Garden,
Instance,
Job,
Parameter,
@@ -722,138 +720,3 @@ def test_blocklist_entries_are_required_to_be_unique(self, command_blocklist):
CommandPublishingBlockList(
namespace=self.namespace, system=self.system, command=self.command
).save()
-
-
-class TestGarden:
- v1_str = "v1"
- v2_str = "v2"
- garden_name = "test_garden"
-
- @classmethod
- def setup_class(cls):
- connect("beer_garden", host="mongomock://localhost")
- Garden.drop_collection()
- Garden.ensure_indexes()
-
- @pytest.fixture()
- def local_garden(self, mongo_conn):
- garden = Garden(name=self.garden_name, connection_type="LOCAL").save()
-
- yield garden
-
- garden.delete()
-
- @pytest.fixture
- def child_system(self):
- return System(name="echoer", namespace="child_garden")
-
- @pytest.fixture
- def child_system_v1(self, child_system):
- system: System = copy.deepcopy(child_system)
- system.version = self.v1_str
- system.save()
-
- yield system
-
- system.delete()
-
- @pytest.fixture
- def child_system_v2(self, child_system):
- system: System = copy.deepcopy(child_system)
- system.version = self.v2_str
- system.save()
-
- yield system
-
- system.delete()
-
- @pytest.fixture
- def child_system_v1_diff_id(self, child_system):
- system: System = copy.deepcopy(child_system)
- system.version = self.v1_str
- system.save()
-
- yield system
-
- system.delete()
-
- @pytest.fixture
- def child_garden(self, child_system_v1):
- garden = Garden(
- name="child_garden", connection_type="http", systems=[child_system_v1]
- ).save()
-
- yield garden
-
- garden.delete()
-
- def test_garden_names_are_required_to_be_unique(self, local_garden):
- """Attempting to create a garden that shares a name with an existing garden
- should raise an exception"""
- with pytest.raises(NotUniqueError):
- Garden(name=local_garden.name, connection_type="HTTP").save()
-
- def test_only_one_local_garden_may_exist(self, local_garden):
- """Attempting to create more than one garden with connection_type of LOCAL
- should raise an exception"""
- with pytest.raises(NotUniqueError):
- Garden(name=f"not{local_garden.name}", connection_type="LOCAL").save()
-
- def test_child_garden_system_attrib_update(self, child_garden, child_system_v2):
- """If the systems of a child garden are updated such that their names,
- namespaces, or versions are changed, the original systems are removed and
- replaced with the new systems when the garden is saved."""
- orig_system_ids = set(
- map(lambda x: str(getattr(x, "id")), child_garden.systems) # noqa: B009
- )
- orig_system_versions = set(
- map(
- lambda x: str(getattr(x, "version")), child_garden.systems # noqa: B009
- )
- )
-
- assert (
- self.v1_str in orig_system_versions
- and self.v2_str not in orig_system_versions
- )
-
- child_garden.systems = [child_system_v2]
- child_garden.deep_save()
-
- # we check that the garden written to the DB has the correct systems
- db_garden = Garden.objects().first()
-
- new_system_ids = set(
- map(lambda x: str(getattr(x, "id")), db_garden.systems) # noqa: B009
- )
- new_system_versions = set(
- map(lambda x: str(getattr(x, "version")), db_garden.systems) # noqa: B009
- )
-
- assert (
- self.v1_str not in new_system_versions
- and self.v2_str in new_system_versions
- )
- assert new_system_ids.intersection(orig_system_ids) == set()
-
- def test_child_garden_system_id_update(self, child_garden, child_system_v1_diff_id):
- """If the systems of a child garden are updated such that the names, namespaces
- and versions remain constant, but the IDs are different, the original systms
- are removed and replaced with the new systems when the garden is saved."""
- orig_system_ids = set(
- map(lambda x: str(getattr(x, "id")), child_garden.systems) # noqa: B009
- )
- new_system_id = str(child_system_v1_diff_id.id)
-
- assert new_system_id not in orig_system_ids
-
- child_garden.systems = [child_system_v1_diff_id]
- child_garden.deep_save()
- db_garden = Garden.objects().first()
-
- new_system_ids = set(
- map(lambda x: str(getattr(x, "id")), db_garden.systems) # noqa: B009
- )
-
- assert new_system_id in new_system_ids
- assert orig_system_ids.intersection(new_system_ids) == set()
diff --git a/src/app/test/garden_test.py b/src/app/test/garden_test.py
index a4403b5ee..611f34853 100644
--- a/src/app/test/garden_test.py
+++ b/src/app/test/garden_test.py
@@ -10,6 +10,7 @@
from beer_garden.db.mongo.models import Garden, System
from beer_garden.garden import (
create_garden,
+ get_connection_defaults,
get_garden,
get_gardens,
local_garden,
@@ -124,9 +125,8 @@ def test_remove_garden_removes_related_systems(self, localgarden, remotegarden):
# confirm that systems of other gardens remain intact
assert len(System.objects.filter(namespace=localgarden.name)) == 1
- def test_create_garden_loads_default_config(self, bg_garden):
+ def test_create_garden_loads_default_config(self, remotegarden):
"""create_garden should explicitly load default HTTP configs from brewtils"""
-
http_params = {
"host": "localhost",
"port": 1337,
@@ -137,15 +137,15 @@ def test_create_garden_loads_default_config(self, bg_garden):
"client_cert": "/def",
}
- bg_garden.connection_params = {"http": http_params}
+ remotegarden.connection_params = {"http": http_params}
- garden = create_garden(bg_garden)
+ garden = create_garden(remotegarden)
for key in http_params:
assert garden.connection_params["http"][key] == http_params[key]
- def test_create_garden_with_empty_connection_params(self, bg_garden):
- """create_garden should explicitly load default HTTP configs from brewtils when empty"""
-
+ def test_create_garden_with_empty_connection_params(self, remotegarden):
+ """create_garden should explicitly load default HTTP configs from brewtils when
+ empty"""
config_map = {
"bg_host": "host",
"bg_port": "port",
@@ -156,10 +156,8 @@ def test_create_garden_with_empty_connection_params(self, bg_garden):
"client_cert": "client_cert",
}
- spec = YapconfSpec(_CONNECTION_SPEC)
- # bg_host is required by brewtils garden spec
- defaults = spec.load_config({"bg_host": ""})
+ defaults = get_connection_defaults()
+ garden = create_garden(remotegarden)
- garden = create_garden(bg_garden)
for key in config_map:
assert garden.connection_params["http"][config_map[key]] == defaults[key]
From 97c5701395ce6dba68eab59b9bc9ceab01c1c069 Mon Sep 17 00:00:00 2001
From: John B
Date: Fri, 17 Dec 2021 11:15:51 -0500
Subject: [PATCH 2/7] #1141 - Garden connection import/export
---
src/app/beer_garden/api/http/base_handler.py | 67 +++++++++-
src/app/beer_garden/api/http/client.py | 11 +-
.../api/http/handlers/v1/garden.py | 36 ++++--
.../beer_garden/db/schemas/garden_schema.py | 5 +-
src/app/beer_garden/events/handlers.py | 27 ++--
src/app/beer_garden/garden.py | 35 ++---
.../handlers/v1/garden_connection_test.py | 120 ++++++++++++++++++
.../test/db/mongo/models/garden_model_test.py | 8 +-
src/app/test/garden_test.py | 2 -
9 files changed, 255 insertions(+), 56 deletions(-)
create mode 100644 src/app/test/api/http/unit/handlers/v1/garden_connection_test.py
diff --git a/src/app/beer_garden/api/http/base_handler.py b/src/app/beer_garden/api/http/base_handler.py
index 37afcd982..632b3717c 100644
--- a/src/app/beer_garden/api/http/base_handler.py
+++ b/src/app/beer_garden/api/http/base_handler.py
@@ -4,7 +4,7 @@
import json
import re
import socket
-from typing import Type, Union
+from typing import Any, Dict, Text, Type, Union
from brewtils.errors import (
AuthorizationRequired,
@@ -198,7 +198,7 @@ def write_error(self, status_code, **kwargs):
message = error_dict.get("message", getattr(e, "message", str(e)))
code = error_dict.get("status_code", 500)
elif issubclass(typ3, BaseHTTPError):
- message = typ3.reason
+ message = self._reason
code = typ3.status_code
elif config.get("ui.debug_mode"):
message = str(e)
@@ -210,7 +210,7 @@ def write_error(self, status_code, **kwargs):
)
self.set_header("Content-Type", "application/json; charset=UTF-8")
- self.set_status(code)
+ self.set_status(code, reason=message)
self.finish({"message": message})
@property
@@ -250,3 +250,64 @@ def schema_validated_body(self, schema: Type[Schema]) -> dict:
return schema(strict=True).load(self.request_body).data
except MarshmallowValidationError:
raise BadRequest
+
+ def load_or_raise(
+ self,
+ schema: Type[Schema],
+ arg: Any,
+ from_string: bool = True,
+ many: bool = False,
+ ):
+ """Apply a schema to an argument or raise a validation exception.
+
+ This is used to validate user-provided data.
+
+ Args:
+ schema: A schema derived from a marshmallow Schema that the argument
+ will be validated against
+ arg: The data to validate
+ from_string: Process `arg` as string if `True`
+ many: Process `arg` as a list of objects if `True`
+
+ Returns:
+ The result of deserializing the data
+
+ Raises:
+ BadRequest: The supplied data failed to validate against the schema
+ """
+ schema = schema(strict=True, many=many)
+
+ try:
+ return schema.loads(arg).data if from_string else schema.load(arg).data
+ except MarshmallowValidationError as mmve:
+ raise BadRequest(reason=str(mmve))
+
+ def format_response(
+ self, schema: Type[Schema], arg: Any, to_string: bool = True, many: bool = False
+ ) -> Union[Text, Dict]:
+ """Apply a schema to an argument or raise an internal error.
+
+ This is used to format internal models into serialized format, which could
+ potentially fail validation in cases where bad data was allowed into the
+ database.
+
+ Args:
+ schema: A schema derived from a marshmallow Schema that the argment
+ will be validated against
+ arg: The data to validate
+ to_string: Convert `arg` to string if `True`
+ many: The `arg` will be treated as a list of multiple objects if `True`
+
+ Returns:
+ The result of serializing the data
+
+ Raises:
+ MarshmallowValidationError: The supplied data failed to validate against the
+ schema
+
+ """
+ return (
+ schema(strict=True, many=many).dumps(arg).data
+ if to_string
+ else schema(strict=True, many=many).dump(arg).data
+ )
diff --git a/src/app/beer_garden/api/http/client.py b/src/app/beer_garden/api/http/client.py
index 26aa431b5..32b159b0c 100644
--- a/src/app/beer_garden/api/http/client.py
+++ b/src/app/beer_garden/api/http/client.py
@@ -30,11 +30,14 @@ async def __call__(self, *args, serialize_kwargs=None, **kwargs):
if serialize_kwargs.get("return_raw") or isinstance(result, six.string_types):
return result
- if self.json_dump(result):
- return json.dumps(result) if serialize_kwargs["to_string"] else result
-
if isinstance(result, BrewtilsGarden):
- return GardenSchema(strict=True).dumps(result).data
+ return (
+ GardenSchema(strict=True).dumps(result).data
+ if serialize_kwargs["to_string"]
+ else GardenSchema(strict=True).dump(result).data
+ )
+ elif self.json_dump(result):
+ return json.dumps(result) if serialize_kwargs["to_string"] else result
return SchemaParser.serialize(result, **(serialize_kwargs or {}))
diff --git a/src/app/beer_garden/api/http/handlers/v1/garden.py b/src/app/beer_garden/api/http/handlers/v1/garden.py
index c08e0145f..9ce4c7676 100644
--- a/src/app/beer_garden/api/http/handlers/v1/garden.py
+++ b/src/app/beer_garden/api/http/handlers/v1/garden.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
+import logging
+
from brewtils.errors import ModelValidationError
-from brewtils.models import Operation
+from brewtils.models import Operation as BrewtilsOperation
from brewtils.schema_parser import SchemaParser
from mongoengine.queryset.queryset import QuerySet
@@ -10,6 +12,8 @@
from beer_garden.db.schemas.garden_schema import GardenSchema
from beer_garden.garden import local_garden
+logger = logging.getLogger(__name__)
+
GARDEN_CREATE = Permissions.GARDEN_CREATE.value
GARDEN_READ = Permissions.GARDEN_READ.value
GARDEN_UPDATE = Permissions.GARDEN_UPDATE.value
@@ -41,7 +45,7 @@ async def get(self, garden_name):
"""
garden = self.get_or_raise(Garden, GARDEN_READ, name=garden_name)
- response = GardenSchema(strict=True).dumps(garden).data
+ response = self.format_response(GardenSchema, garden)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(response)
@@ -68,7 +72,9 @@ async def delete(self, garden_name):
"""
garden = self.get_or_raise(Garden, GARDEN_DELETE, name=garden_name)
- await self.client(Operation(operation_type="GARDEN_DELETE", args=[garden.name]))
+ await self.client(
+ BrewtilsOperation(operation_type="GARDEN_DELETE", args=[garden.name])
+ )
self.set_status(204)
@@ -126,30 +132,35 @@ async def patch(self, garden_name):
if operation in ["initializing", "running", "stopped", "block"]:
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_UPDATE_STATUS",
args=[garden.name, operation.upper()],
)
)
elif operation == "heartbeat":
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_UPDATE_STATUS",
args=[garden.name, "RUNNING"],
)
)
elif operation == "config":
- garden_to_update = GardenSchema(strict=True).load(op.value).data
+ garden_to_update = self.load_or_raise(
+ GardenSchema, op.value, from_string=False
+ )
+ # don't let a name change sneak through here
+ garden_to_update.name = garden_name
garden_to_update.id = garden.id
+
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_UPDATE_CONFIG",
args=[garden_to_update],
)
)
elif operation == "sync":
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_SYNC",
kwargs={"sync_target": garden.name},
)
@@ -182,8 +193,7 @@ async def get(self):
- Garden
"""
permitted_gardens: QuerySet = self.permissioned_queryset(Garden, GARDEN_READ)
-
- response = GardenSchema(strict=True, many=True).dumps(permitted_gardens).data
+ response = self.format_response(GardenSchema, permitted_gardens, many=True)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(response)
@@ -210,12 +220,12 @@ async def post(self):
tags:
- Garden
"""
- garden = GardenSchema(strict=True).loads(self.request.decoded_body).data
+ garden = self.load_or_raise(GardenSchema, self.request.decoded_body)
self.verify_user_permission_for_object(GARDEN_CREATE, garden)
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_CREATE",
args=[garden],
)
@@ -275,7 +285,7 @@ async def patch(self):
if operation == "sync":
response = await self.client(
- Operation(
+ BrewtilsOperation(
operation_type="GARDEN_SYNC",
)
)
diff --git a/src/app/beer_garden/db/schemas/garden_schema.py b/src/app/beer_garden/db/schemas/garden_schema.py
index 4c3d509b3..5975c07f4 100644
--- a/src/app/beer_garden/db/schemas/garden_schema.py
+++ b/src/app/beer_garden/db/schemas/garden_schema.py
@@ -4,8 +4,7 @@
from brewtils.schemas import StatusInfoSchema # noqa # until we can fully decouple
from brewtils.schemas import SystemSchema # noqa # until we can fully decouple
from marshmallow import Schema, ValidationError, fields
-from marshmallow.decorators import post_load, pre_load, validates_schema
-from mongoengine.queryset.queryset import QuerySet
+from marshmallow.decorators import post_load, validates_schema
logger = logging.getLogger(__name__)
@@ -95,7 +94,7 @@ class GardenSchema(GardenBaseSchema):
name = fields.Str(allow_none=False)
status = fields.Str(allow_none=True)
status_info = fields.Nested(StatusInfoSchema, allow_none=True)
- connection_type = fields.Str(allow_none=False)
+ connection_type = fields.Str(allow_none=True)
connection_params = fields.Nested(
"GardenConnectionsParamsSchema",
allow_none=True,
diff --git a/src/app/beer_garden/events/handlers.py b/src/app/beer_garden/events/handlers.py
index 772608a21..4d6b5b267 100644
--- a/src/app/beer_garden/events/handlers.py
+++ b/src/app/beer_garden/events/handlers.py
@@ -34,19 +34,22 @@ def garden_callbacks(event: Event) -> None:
logger.debug(f"{event!r}")
# These are all the MAIN PROCESS subsystems that care about events
- for handler in [
- beer_garden.application.handle_event,
- beer_garden.garden.handle_event,
- beer_garden.plugin.handle_event,
- beer_garden.requests.handle_event,
- beer_garden.router.handle_event,
- beer_garden.systems.handle_event,
- beer_garden.scheduler.handle_event,
- beer_garden.log.handle_event,
- beer_garden.files.handle_event,
- beer_garden.local_plugins.manager.handle_event,
+ for handler, handler_tag in [
+ (beer_garden.application.handle_event, "Application"),
+ (beer_garden.garden.handle_event, "Garden"),
+ (beer_garden.plugin.handle_event, "Plugin"),
+ (beer_garden.requests.handle_event, "Request"),
+ (beer_garden.router.handle_event, "Rounter"),
+ (beer_garden.systems.handle_event, "System"),
+ (beer_garden.scheduler.handle_event, "Scheduler"),
+ (beer_garden.log.handle_event, "Log"),
+ (beer_garden.files.handle_event, "File"),
+ (beer_garden.local_plugins.manager.handle_event, "Local plugins manager"),
]:
try:
handler(deepcopy(event))
except Exception as ex:
- logger.exception(f"Error executing callback for {event!r}: {ex}")
+ logger.exception(
+ f"'{handler_tag}' event handler received an error executing callback"
+ f" for {event!r}: {ex}"
+ )
diff --git a/src/app/beer_garden/garden.py b/src/app/beer_garden/garden.py
index 3bdf2a3af..555be2bde 100644
--- a/src/app/beer_garden/garden.py
+++ b/src/app/beer_garden/garden.py
@@ -186,29 +186,34 @@ def get_connection_defaults():
# TODO: this is a temporary work-around until Brewtils is configured to provide
# sensible defaults
+ bad_defaults = {"ssl_enabled", "ca_verify"}
sensible_defaults = {
- "bg_host": "somehostname",
+ "bg_host": "child_hostname",
"bg_port": 1025,
"ssl_enabled": False,
"bg_url_prefix": "/",
- "ca_cert": "none",
"ca_verify": False,
- "client_cert": "none",
}
+
# substitute the sensible default only if we're provided `None` or an empty string
- defaults = {
- key: (
- defaults[key]
- if (
- (defaults[key] is not None and (isinstance(defaults[key], bool)))
- or defaults[key]
- )
- else sensible_defaults[key]
- )
- for key in config_map
- }
+ new_defaults = {}
+ for key in defaults:
+ # setting ssl and ca_verify to `True` by default makes no sense
+ if key in bad_defaults:
+ new_defaults[key] = sensible_defaults[key]
+ else:
+ provided = defaults[key]
+ if key in config_map:
+ if provided is not None and provided:
+ # always use a string that is not empty
+ new_defaults[key] = provided
+ else:
+ # but use the empty string if we don't provide an alternative
+ new_defaults[key] = (
+ sensible_defaults[key] if key in sensible_defaults else ""
+ )
- return defaults
+ return new_defaults
@publish_event(Events.GARDEN_CREATED)
diff --git a/src/app/test/api/http/unit/handlers/v1/garden_connection_test.py b/src/app/test/api/http/unit/handlers/v1/garden_connection_test.py
new file mode 100644
index 000000000..9447418d3
--- /dev/null
+++ b/src/app/test/api/http/unit/handlers/v1/garden_connection_test.py
@@ -0,0 +1,120 @@
+import json
+
+import pytest
+from tornado.httpclient import HTTPRequest, HTTPResponse
+
+from beer_garden.db.mongo.models import Garden
+from beer_garden.db.schemas.garden_schema import GardenSchema
+
+
+@pytest.fixture
+def http_connection_params():
+ return {
+ "http": {
+ "host": "somehost",
+ "port": 10001,
+ "url_prefix": "/",
+ "ca_verify": False,
+ "ssl": False,
+ }
+ }
+
+
+@pytest.fixture
+def stomp_connection_params():
+ return {
+ "stomp": {
+ "host": "somehost",
+ "port": 10001,
+ "ssl": {"use_ssl": False},
+ "headers": [],
+ "send_destination": "sendtohere",
+ "subscribe_destination": "listenhere",
+ "username": "stompuser",
+ "password": "stomppassword",
+ }
+ }
+
+
+@pytest.fixture
+def garden_with_http_connection_params(http_connection_params):
+ garden = Garden(
+ name="somehttpgardenname",
+ connection_type="HTTP",
+ connection_params=http_connection_params,
+ ).save()
+
+ yield garden
+
+ garden.delete()
+
+
+@pytest.fixture
+def garden_with_stomp_connection_params(stomp_connection_params):
+ garden = Garden(
+ name="somestompgardenname",
+ connection_type="STOMP",
+ connection_params=stomp_connection_params,
+ ).save()
+
+ yield garden
+
+ garden.delete()
+
+
+class TestGardenConnections:
+ @pytest.mark.parametrize(
+ "garden, required_fields, endpoint",
+ (
+ (
+ pytest.lazy_fixture("garden_with_http_connection_params"),
+ {"host", "port", "ssl"},
+ "http",
+ ),
+ (
+ pytest.lazy_fixture("garden_with_stomp_connection_params"),
+ {"host", "port"},
+ "stomp",
+ ),
+ ),
+ )
+ @pytest.mark.gen_test
+ def test_import_with_missing_required_params_returns_useful_message(
+ self,
+ garden,
+ required_fields,
+ endpoint,
+ http_client,
+ base_url,
+ ):
+ missing_data_message = "Missing data for required field." # default marshmallow
+
+ endpoint_connection_params = garden.connection_params.pop(endpoint)
+ for required_field in required_fields:
+ _ = endpoint_connection_params.pop(required_field)
+
+ garden.connection_params = {endpoint: endpoint_connection_params}
+ patch_request_body = f"""{{"operations": [
+ {{
+ "operation": "config",
+ "value": {GardenSchema(strict=True).dumps(garden).data}
+ }}
+ ]}}
+ """
+
+ url = f"{base_url}/api/v1/gardens/" + garden.name
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
+
+ request = HTTPRequest(
+ url, method="PATCH", headers=headers, body=patch_request_body
+ )
+
+ response: HTTPResponse
+ response = yield http_client.fetch(request, raise_error=False)
+ error_dict = json.loads(response.error.message.replace("'", '"'))[
+ "connection_params"
+ ][endpoint]
+
+ for field in required_fields:
+ assert field in error_dict
+ assert error_dict[field].pop() == missing_data_message
diff --git a/src/app/test/db/mongo/models/garden_model_test.py b/src/app/test/db/mongo/models/garden_model_test.py
index 3afcbd7a7..c44bbbb82 100644
--- a/src/app/test/db/mongo/models/garden_model_test.py
+++ b/src/app/test/db/mongo/models/garden_model_test.py
@@ -225,8 +225,8 @@ def test_local_garden_save_succeeds_with_empty_conn_params(self):
"conn_parm",
(
pytest.lazy_fixture("bad_conn_params"),
- # pytest.lazy_fixture("bad_conn_params_with_partial_good"),
- # pytest.lazy_fixture("bad_conn_params_with_full_good"),
+ pytest.lazy_fixture("bad_conn_params_with_partial_good"),
+ pytest.lazy_fixture("bad_conn_params_with_full_good"),
),
)
def test_remote_garden_save_fails_with_bad_conn_params(self, conn_parm):
@@ -292,7 +292,7 @@ def test_remote_garden_save_succeeds_with_only_good_stomp_headers(
"bad_headers",
(
garbage_headers_extra_key,
- # garbage_headers_wrong_key,
+ garbage_headers_wrong_key,
),
)
def test_remote_garden_save_fails_with_garbage_stomp_headers(
@@ -302,7 +302,7 @@ def test_remote_garden_save_fails_with_garbage_stomp_headers(
test_params["headers"] = bad_headers
connection_params = {"stomp": test_params}
- with pytest.raises(ValidationError) as exc:
+ with pytest.raises(ValidationError):
Garden(
name=garden_name,
connection_type="STOMP",
diff --git a/src/app/test/garden_test.py b/src/app/test/garden_test.py
index 611f34853..89651d9f2 100644
--- a/src/app/test/garden_test.py
+++ b/src/app/test/garden_test.py
@@ -2,9 +2,7 @@
import pytest
from brewtils.models import Garden as BrewtilsGarden
from brewtils.models import System as BrewtilsSystem
-from brewtils.specification import _CONNECTION_SPEC
from mongoengine import DoesNotExist, connect
-from yapconf import YapconfSpec
from beer_garden import config
from beer_garden.db.mongo.models import Garden, System
From 5c2b7b0eb2658e8c6788def2b297f3c207b2b1ae Mon Sep 17 00:00:00 2001
From: John B
Date: Wed, 5 Jan 2022 08:59:46 -0500
Subject: [PATCH 3/7] Typo
---
src/app/beer_garden/events/handlers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/beer_garden/events/handlers.py b/src/app/beer_garden/events/handlers.py
index 4d6b5b267..945d57cc6 100644
--- a/src/app/beer_garden/events/handlers.py
+++ b/src/app/beer_garden/events/handlers.py
@@ -39,7 +39,7 @@ def garden_callbacks(event: Event) -> None:
(beer_garden.garden.handle_event, "Garden"),
(beer_garden.plugin.handle_event, "Plugin"),
(beer_garden.requests.handle_event, "Request"),
- (beer_garden.router.handle_event, "Rounter"),
+ (beer_garden.router.handle_event, "Router"),
(beer_garden.systems.handle_event, "System"),
(beer_garden.scheduler.handle_event, "Scheduler"),
(beer_garden.log.handle_event, "Log"),
From defe44e1ed2f7ff80b1800ffa9eb790becea3549 Mon Sep 17 00:00:00 2001
From: John B
Date: Wed, 5 Jan 2022 12:19:37 -0500
Subject: [PATCH 4/7] Massive eslint refactor
---
src/ui/src/index.js | 396 +++++-----
src/ui/src/js/configs/compiler_config.js | 4 +-
src/ui/src/js/configs/dt_renderer.js | 206 ++---
src/ui/src/js/configs/http_interceptor.js | 62 +-
src/ui/src/js/configs/routes.js | 470 ++++++------
src/ui/src/js/controllers/about.js | 18 +-
src/ui/src/js/controllers/admin_garden.js | 71 +-
.../src/js/controllers/admin_garden_view.js | 80 +-
src/ui/src/js/controllers/admin_queue.js | 69 +-
src/ui/src/js/controllers/admin_role.js | 328 ++++----
src/ui/src/js/controllers/admin_system.js | 151 ++--
.../controllers/admin_system_force_delete.js | 42 +-
.../src/js/controllers/admin_system_logs.js | 95 +--
src/ui/src/js/controllers/admin_user.js | 224 +++---
src/ui/src/js/controllers/command_index.js | 132 ++--
src/ui/src/js/controllers/command_view.js | 232 +++---
.../src/js/controllers/job/create_command.js | 4 +-
.../src/js/controllers/job/create_request.js | 106 +--
.../src/js/controllers/job/create_system.js | 4 +-
.../src/js/controllers/job/create_trigger.js | 132 ++--
src/ui/src/js/controllers/job/export_jobs.js | 54 +-
src/ui/src/js/controllers/job/import_jobs.js | 73 +-
src/ui/src/js/controllers/job_index.js | 12 +-
src/ui/src/js/controllers/job_view.js | 120 +--
src/ui/src/js/controllers/login.js | 48 +-
src/ui/src/js/controllers/request_index.js | 364 ++++-----
src/ui/src/js/controllers/request_view.js | 267 +++----
src/ui/src/js/controllers/system_index.js | 123 +--
src/ui/src/js/directives/custom_on_change.js | 10 +-
src/ui/src/js/directives/fetch_data.js | 60 +-
src/ui/src/js/directives/system_status.js | 36 +-
src/ui/src/js/run.js | 174 ++---
src/ui/src/js/services/admin_service.js | 4 +-
src/ui/src/js/services/command_service.js | 5 +-
src/ui/src/js/services/error_service.js | 62 +-
src/ui/src/js/services/event_service.js | 14 +-
src/ui/src/js/services/garden_service.js | 321 ++++----
src/ui/src/js/services/instance_service.js | 20 +-
src/ui/src/js/services/job_service.js | 714 +++++++++---------
src/ui/src/js/services/namespace_service.js | 2 +-
src/ui/src/js/services/permission_service.js | 4 +-
src/ui/src/js/services/queue_service.js | 12 +-
src/ui/src/js/services/request_service.js | 70 +-
src/ui/src/js/services/role_service.js | 56 +-
src/ui/src/js/services/runner_service.js | 20 +-
src/ui/src/js/services/system_service.js | 85 +--
src/ui/src/js/services/token_service.js | 62 +-
src/ui/src/js/services/user_service.js | 38 +-
src/ui/src/js/services/utility_service.js | 70 +-
49 files changed, 2866 insertions(+), 2860 deletions(-)
diff --git a/src/ui/src/index.js b/src/ui/src/index.js
index 9a15f9869..a3aa3bbc7 100644
--- a/src/ui/src/index.js
+++ b/src/ui/src/index.js
@@ -1,238 +1,238 @@
-"use strict";
+'use strict';
// Javascript
// Utilities
-import "babel-polyfill";
-import "objectpath";
-import "tv4";
-import "jquery";
+import 'babel-polyfill';
+import 'objectpath';
+import 'tv4';
+import 'jquery';
-import moment from "moment";
-import "moment-timezone";
-moment.tz.setDefault("UTC");
+import moment from 'moment';
+import 'moment-timezone';
+moment.tz.setDefault('UTC');
// Angular
-import angular from "angular";
-import "angular-animate";
-import "angular-confirm";
-import "angular-filter";
-import "angular-sanitize";
-import "angular-ui-ace";
-import "angular-ui-bootstrap";
-import "@uirouter/angularjs";
-import "angular-bootstrap-switch";
-import "angular-strap";
-import "angular-local-storage";
-
-import "bootstrap";
-import "bootstrap-switch";
-import "eonasdan-bootstrap-datetimepicker";
-import "ui-select";
-import "metismenu";
-import "startbootstrap-sb-admin-2/js/sb-admin-2.js";
-import "datatables.net";
-import "datatables.net-bs";
-import "datatables-columnfilter";
-import "datatables-columnfilter/dist/dataTables.lcf.eonasdan.js";
-import "jwt-decode";
-import "angular-datatables/dist/angular-datatables.js";
-import "angular-datatables/dist/plugins/light-columnfilter/angular-datatables.light-columnfilter.js"; // eslint-disable-line max-len
-import "angular-datatables/dist/plugins/bootstrap/angular-datatables.bootstrap.js";
-import "angular-schema-form-bootstrap/dist/angular-schema-form-bootstrap-bundled.js";
-import "ace-builds/src-noconflict/ace.js";
-import "ace-builds/src-noconflict/mode-json.js";
-import "ace-builds/src-noconflict/theme-dawn.js";
+import angular from 'angular';
+import 'angular-animate';
+import 'angular-confirm';
+import 'angular-filter';
+import 'angular-sanitize';
+import 'angular-ui-ace';
+import 'angular-ui-bootstrap';
+import '@uirouter/angularjs';
+import 'angular-bootstrap-switch';
+import 'angular-strap';
+import 'angular-local-storage';
+
+import 'bootstrap';
+import 'bootstrap-switch';
+import 'eonasdan-bootstrap-datetimepicker';
+import 'ui-select';
+import 'metismenu';
+import 'startbootstrap-sb-admin-2/js/sb-admin-2.js';
+import 'datatables.net';
+import 'datatables.net-bs';
+import 'datatables-columnfilter';
+import 'datatables-columnfilter/dist/dataTables.lcf.eonasdan.js';
+import 'jwt-decode';
+import 'angular-datatables/dist/angular-datatables.js';
+import 'angular-datatables/dist/plugins/light-columnfilter/angular-datatables.light-columnfilter.js'; // eslint-disable-line max-len
+import 'angular-datatables/dist/plugins/bootstrap/angular-datatables.bootstrap.js';
+import 'angular-schema-form-bootstrap/dist/angular-schema-form-bootstrap-bundled.js';
+import 'ace-builds/src-noconflict/ace.js';
+import 'ace-builds/src-noconflict/mode-json.js';
+import 'ace-builds/src-noconflict/theme-dawn.js';
// Our ASF addons and builder
-import "@beer-garden/builder";
-import "@beer-garden/addons";
+import '@beer-garden/builder';
+import '@beer-garden/addons';
// TODO - This needs to be served separately right now, something about WebWorkers?
// require('ace-builds/src-noconflict/worker-json.js');
// CSS
-import "bootstrap/dist/css/bootstrap.css";
-import "bootstrap/dist/css/bootstrap-theme.css";
-import "bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css";
-import "metismenu/dist/metisMenu.css";
-import "startbootstrap-sb-admin-2/dist/css/sb-admin-2.css";
-import "datatables.net-bs/css/dataTables.bootstrap.css";
-import "ui-select/dist/select.css";
-import "font-awesome/css/font-awesome.css";
-import "./styles/custom.css";
+import 'bootstrap/dist/css/bootstrap.css';
+import 'bootstrap/dist/css/bootstrap-theme.css';
+import 'bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css';
+import 'metismenu/dist/metisMenu.css';
+import 'startbootstrap-sb-admin-2/dist/css/sb-admin-2.css';
+import 'datatables.net-bs/css/dataTables.bootstrap.css';
+import 'ui-select/dist/select.css';
+import 'font-awesome/css/font-awesome.css';
+import './styles/custom.css';
// Now load our actual application components
-import appRun from "./js/run.js";
-import runDTRenderer from "./js/configs/dt_renderer.js";
-import routeConfig from "./js/configs/routes.js";
+import appRun from './js/run.js';
+import runDTRenderer from './js/configs/dt_renderer.js';
+import routeConfig from './js/configs/routes.js';
import {
interceptorService,
authInterceptorService,
interceptorConfig,
-} from "./js/configs/http_interceptor.js";
-
-import { compilerConfig } from "./js/configs/compiler_config.js";
-
-import fetchDataDirective from "./js/directives/fetch_data.js";
-import bgStatusDirective from "./js/directives/system_status.js";
-import customOnChangeDirective from "./js/directives/custom_on_change.js";
-
-import adminService from "./js/services/admin_service.js";
-import commandService from "./js/services/command_service.js";
-import instanceService from "./js/services/instance_service.js";
-import queueService from "./js/services/queue_service.js";
-import requestService from "./js/services/request_service.js";
-import systemService from "./js/services/system_service.js";
-import userService from "./js/services/user_service.js";
-import roleService from "./js/services/role_service.js";
-import permissionService from "./js/services/permission_service.js";
-import tokenService from "./js/services/token_service.js";
-import utilityService from "./js/services/utility_service.js";
-import jobService from "./js/services/job_service.js";
-import errorService from "./js/services/error_service.js";
-import eventService from "./js/services/event_service.js";
-import namespaceService from "./js/services/namespace_service.js";
-import gardenService from "./js/services/garden_service.js";
-import runnerService from "./js/services//runner_service.js";
-
-import aboutController from "./js/controllers/about.js";
-import adminQueueController from "./js/controllers/admin_queue.js";
-import adminSystemController from "./js/controllers/admin_system.js";
-import adminSystemLogsController from "./js/controllers/admin_system_logs.js";
-import adminSystemForceDeleteController from "./js/controllers/admin_system_force_delete.js";
+} from './js/configs/http_interceptor.js';
+
+import {compilerConfig} from './js/configs/compiler_config.js';
+
+import fetchDataDirective from './js/directives/fetch_data.js';
+import bgStatusDirective from './js/directives/system_status.js';
+import customOnChangeDirective from './js/directives/custom_on_change.js';
+
+import adminService from './js/services/admin_service.js';
+import commandService from './js/services/command_service.js';
+import instanceService from './js/services/instance_service.js';
+import queueService from './js/services/queue_service.js';
+import requestService from './js/services/request_service.js';
+import systemService from './js/services/system_service.js';
+import userService from './js/services/user_service.js';
+import roleService from './js/services/role_service.js';
+import permissionService from './js/services/permission_service.js';
+import tokenService from './js/services/token_service.js';
+import utilityService from './js/services/utility_service.js';
+import jobService from './js/services/job_service.js';
+import errorService from './js/services/error_service.js';
+import eventService from './js/services/event_service.js';
+import namespaceService from './js/services/namespace_service.js';
+import gardenService from './js/services/garden_service.js';
+import runnerService from './js/services//runner_service.js';
+
+import aboutController from './js/controllers/about.js';
+import adminQueueController from './js/controllers/admin_queue.js';
+import adminSystemController from './js/controllers/admin_system.js';
+import adminSystemLogsController from './js/controllers/admin_system_logs.js';
+import adminSystemForceDeleteController from './js/controllers/admin_system_force_delete.js';
import {
adminUserController,
newUserController,
-} from "./js/controllers/admin_user.js";
+} from './js/controllers/admin_user.js';
import {
adminRoleController,
newRoleController,
-} from "./js/controllers/admin_role.js";
-import adminGardenController from "./js/controllers/admin_garden.js";
-import adminGardenViewController from "./js/controllers/admin_garden_view.js";
-import commandIndexController from "./js/controllers/command_index.js";
-import commandViewController from "./js/controllers/command_view.js";
-import requestIndexController from "./js/controllers/request_index.js";
+} from './js/controllers/admin_role.js';
+import adminGardenController from './js/controllers/admin_garden.js';
+import adminGardenViewController from './js/controllers/admin_garden_view.js';
+import commandIndexController from './js/controllers/command_index.js';
+import commandViewController from './js/controllers/command_view.js';
+import requestIndexController from './js/controllers/request_index.js';
import requestViewController, {
slideAnimation,
-} from "./js/controllers/request_view.js";
-import systemIndexController from "./js/controllers/system_index.js";
-import jobIndexController from "./js/controllers/job_index.js";
+} from './js/controllers/request_view.js';
+import systemIndexController from './js/controllers/system_index.js';
+import jobIndexController from './js/controllers/job_index.js';
import {
jobViewController,
jobRunNowModalController,
-} from "./js/controllers/job_view.js";
-import jobCreateSystemController from "./js/controllers/job/create_system.js";
-import jobExportController from "./js/controllers/job/export_jobs.js";
+} from './js/controllers/job_view.js';
+import jobCreateSystemController from './js/controllers/job/create_system.js';
+import jobExportController from './js/controllers/job/export_jobs.js';
import {
jobImportController,
jobImportModalController,
-} from "./js/controllers/job/import_jobs.js";
-import jobCreateCommandController from "./js/controllers/job/create_command.js";
-import jobCreateRequestController from "./js/controllers/job/create_request.js";
-import jobCreateTriggerController from "./js/controllers/job/create_trigger.js";
-import loginController from "./js/controllers/login.js";
+} from './js/controllers/job/import_jobs.js';
+import jobCreateCommandController from './js/controllers/job/create_command.js';
+import jobCreateRequestController from './js/controllers/job/create_request.js';
+import jobCreateTriggerController from './js/controllers/job/create_trigger.js';
+import loginController from './js/controllers/login.js';
// Partials
-import "./partials/about.html";
-import "./partials/admin_system.html";
-import "./partials/admin_user.html";
-import "./partials/admin_role.html";
-import "./partials/admin_garden_index.html";
-import "./partials/admin_garden_view.html";
-import "./partials/command_index.html";
-import "./partials/command_view.html";
-import "./partials/request_index.html";
-import "./partials/request_view.html";
-import "./partials/system_index.html";
-import "./partials/job_index.html";
-import "./partials/job_view.html";
-import "./partials/job/create_system.html";
-import "./partials/job/create_command.html";
-import "./partials/job/create_request.html";
-import "./partials/job/create_trigger.html";
+import './partials/about.html';
+import './partials/admin_system.html';
+import './partials/admin_user.html';
+import './partials/admin_role.html';
+import './partials/admin_garden_index.html';
+import './partials/admin_garden_view.html';
+import './partials/command_index.html';
+import './partials/command_view.html';
+import './partials/request_index.html';
+import './partials/request_view.html';
+import './partials/system_index.html';
+import './partials/job_index.html';
+import './partials/job_view.html';
+import './partials/job/create_system.html';
+import './partials/job/create_command.html';
+import './partials/job/create_request.html';
+import './partials/job/create_trigger.html';
// Images
-import "./image/fa-beer.png";
-import "./image/fa-coffee.png";
+import './image/fa-beer.png';
+import './image/fa-coffee.png';
// Finally, FINALLY, we have all our dependencies imported. Create the Angularness!
angular
- .module("bgApp", [
- "ui.router",
- "ui.bootstrap",
- "ui.ace",
- "datatables",
- "datatables.bootstrap",
- "datatables.light-columnfilter",
- "schemaForm",
- "angular-confirm",
- "angular.filter",
- "ngAnimate",
- "frapontillo.bootstrap-switch",
- "mgcrea.ngStrap",
- "LocalStorageModule",
- "beer-garden.addons",
- "beer-garden.builder",
- ])
- .run(appRun)
- .run(runDTRenderer)
- .config(routeConfig)
- .config(interceptorConfig)
- .config(compilerConfig)
- .service("APIInterceptor", interceptorService)
- .service("authInterceptorService", authInterceptorService)
- .animation(".slide", slideAnimation)
-
- .directive("fetchData", fetchDataDirective)
- .directive("bgStatus", bgStatusDirective)
- .directive("customOnChange", customOnChangeDirective)
-
- .factory("AdminService", adminService)
- .factory("CommandService", commandService)
- .factory("InstanceService", instanceService)
- .factory("QueueService", queueService)
- .factory("RequestService", requestService)
- .factory("SystemService", systemService)
- .factory("UserService", userService)
- .factory("RoleService", roleService)
- .factory("PermissionService", permissionService)
- .factory("TokenService", tokenService)
- .factory("UtilityService", utilityService)
- .factory("JobService", jobService)
- .factory("ErrorService", errorService)
- .factory("EventService", eventService)
- .factory("NamespaceService", namespaceService)
- .factory("GardenService", gardenService)
- .factory("RunnerService", runnerService)
-
- .controller("AboutController", aboutController)
- .controller("AdminQueueController", adminQueueController)
- .controller(
- "AdminSystemForceDeleteController",
- adminSystemForceDeleteController
- )
- .controller("AdminSystemController", adminSystemController)
- .controller("AdminSystemLogsController", adminSystemLogsController)
- .controller("AdminUserController", adminUserController)
- .controller("NewUserController", newUserController)
- .controller("AdminRoleController", adminRoleController)
- .controller("NewRoleController", newRoleController)
- .controller("AdminGardenController", adminGardenController)
- .controller("AdminGardenViewController", adminGardenViewController)
- .controller("CommandIndexController", commandIndexController)
- .controller("CommandViewController", commandViewController)
- .controller("RequestIndexController", requestIndexController)
- .controller("RequestViewController", requestViewController)
- .controller("SystemIndexController", systemIndexController)
- .controller("JobIndexController", jobIndexController)
- .controller("JobViewController", jobViewController)
- .controller("JobRunNowModalController", jobRunNowModalController)
- .controller("JobCreateSystemController", jobCreateSystemController)
- .controller("JobCreateCommandController", jobCreateCommandController)
- .controller("JobCreateRequestController", jobCreateRequestController)
- .controller("JobCreateTriggerController", jobCreateTriggerController)
- .controller("JobExportController", jobExportController)
- .controller("JobImportController", jobImportController)
- .controller("JobImportModalController", jobImportModalController)
- .controller("LoginController", loginController);
+ .module('bgApp', [
+ 'ui.router',
+ 'ui.bootstrap',
+ 'ui.ace',
+ 'datatables',
+ 'datatables.bootstrap',
+ 'datatables.light-columnfilter',
+ 'schemaForm',
+ 'angular-confirm',
+ 'angular.filter',
+ 'ngAnimate',
+ 'frapontillo.bootstrap-switch',
+ 'mgcrea.ngStrap',
+ 'LocalStorageModule',
+ 'beer-garden.addons',
+ 'beer-garden.builder',
+ ])
+ .run(appRun)
+ .run(runDTRenderer)
+ .config(routeConfig)
+ .config(interceptorConfig)
+ .config(compilerConfig)
+ .service('APIInterceptor', interceptorService)
+ .service('authInterceptorService', authInterceptorService)
+ .animation('.slide', slideAnimation)
+
+ .directive('fetchData', fetchDataDirective)
+ .directive('bgStatus', bgStatusDirective)
+ .directive('customOnChange', customOnChangeDirective)
+
+ .factory('AdminService', adminService)
+ .factory('CommandService', commandService)
+ .factory('InstanceService', instanceService)
+ .factory('QueueService', queueService)
+ .factory('RequestService', requestService)
+ .factory('SystemService', systemService)
+ .factory('UserService', userService)
+ .factory('RoleService', roleService)
+ .factory('PermissionService', permissionService)
+ .factory('TokenService', tokenService)
+ .factory('UtilityService', utilityService)
+ .factory('JobService', jobService)
+ .factory('ErrorService', errorService)
+ .factory('EventService', eventService)
+ .factory('NamespaceService', namespaceService)
+ .factory('GardenService', gardenService)
+ .factory('RunnerService', runnerService)
+
+ .controller('AboutController', aboutController)
+ .controller('AdminQueueController', adminQueueController)
+ .controller(
+ 'AdminSystemForceDeleteController',
+ adminSystemForceDeleteController
+ )
+ .controller('AdminSystemController', adminSystemController)
+ .controller('AdminSystemLogsController', adminSystemLogsController)
+ .controller('AdminUserController', adminUserController)
+ .controller('NewUserController', newUserController)
+ .controller('AdminRoleController', adminRoleController)
+ .controller('NewRoleController', newRoleController)
+ .controller('AdminGardenController', adminGardenController)
+ .controller('AdminGardenViewController', adminGardenViewController)
+ .controller('CommandIndexController', commandIndexController)
+ .controller('CommandViewController', commandViewController)
+ .controller('RequestIndexController', requestIndexController)
+ .controller('RequestViewController', requestViewController)
+ .controller('SystemIndexController', systemIndexController)
+ .controller('JobIndexController', jobIndexController)
+ .controller('JobViewController', jobViewController)
+ .controller('JobRunNowModalController', jobRunNowModalController)
+ .controller('JobCreateSystemController', jobCreateSystemController)
+ .controller('JobCreateCommandController', jobCreateCommandController)
+ .controller('JobCreateRequestController', jobCreateRequestController)
+ .controller('JobCreateTriggerController', jobCreateTriggerController)
+ .controller('JobExportController', jobExportController)
+ .controller('JobImportController', jobImportController)
+ .controller('JobImportModalController', jobImportModalController)
+ .controller('LoginController', loginController);
diff --git a/src/ui/src/js/configs/compiler_config.js b/src/ui/src/js/configs/compiler_config.js
index 9f1b73ae7..456c8cc91 100644
--- a/src/ui/src/js/configs/compiler_config.js
+++ b/src/ui/src/js/configs/compiler_config.js
@@ -1,10 +1,10 @@
-compilerConfig.$inject = ["$compileProvider"];
+compilerConfig.$inject = ['$compileProvider'];
/**
* compilerConfig - Angular configuration object for the Angular compiler.
* @param {$compileProvider} $compileProvider Angular's $compileProvider object.
*/
export function compilerConfig($compileProvider) {
$compileProvider.aHrefSanitizationTrustedUrlList(
- /^\s*(https?|ftp|mailto|tel|file|blob):/
+ /^\s*(https?|ftp|mailto|tel|file|blob):/
);
}
diff --git a/src/ui/src/js/configs/dt_renderer.js b/src/ui/src/js/configs/dt_renderer.js
index fd4c84252..86847a222 100644
--- a/src/ui/src/js/configs/dt_renderer.js
+++ b/src/ui/src/js/configs/dt_renderer.js
@@ -1,4 +1,4 @@
-runDTRenderer.$inject = ["DTRendererService"];
+runDTRenderer.$inject = ['DTRendererService'];
/**
* runDTRenderer - Tweak datatables rendering
@@ -6,122 +6,122 @@ runDTRenderer.$inject = ["DTRendererService"];
*/
export default function runDTRenderer(DTRendererService) {
DTRendererService.registerPlugin({
- postRender: function (options, result) {
+ postRender: function(options, result) {
if (options && options.childContainer) {
- let childContainer = $("")
- .attr("id", "childContainer")
- .css("margin-right", "20px")
- .append(
- $("")
- .attr("id", "childCheck")
- .attr("type", "checkbox")
- .css("margin-top", "-4px")
- .change(() => {
- $(".dataTable").dataTable().fnUpdate();
- })
- )
- .append(
- $("
@@ -89,4 +87,4 @@ Welcome to the Request Scheduler
-
\ No newline at end of file
+
From 187d0e7dd9c1fd5d2c98820d91ab9bcfaa23ec43 Mon Sep 17 00:00:00 2001
From: John B
Date: Mon, 10 Jan 2022 16:10:37 -0500
Subject: [PATCH 6/7] Add import/export buttons & functionality
---
src/ui/src/index.js | 10 +-
.../src/js/controllers/admin_garden_view.js | 38 +++-
.../garden/export_garden_config.js | 51 +++++
.../garden/import_garden_config.js | 101 ++++++++++
src/ui/src/js/services/garden_service.js | 189 +++++++++++++-----
src/ui/src/partials/admin_garden_view.html | 9 +-
.../src/templates/import_garden_config.html | 34 ++++
7 files changed, 366 insertions(+), 66 deletions(-)
create mode 100644 src/ui/src/js/controllers/garden/export_garden_config.js
create mode 100644 src/ui/src/js/controllers/garden/import_garden_config.js
create mode 100644 src/ui/src/templates/import_garden_config.html
diff --git a/src/ui/src/index.js b/src/ui/src/index.js
index a3aa3bbc7..ff1b3c788 100644
--- a/src/ui/src/index.js
+++ b/src/ui/src/index.js
@@ -110,6 +110,11 @@ import {
} from './js/controllers/admin_role.js';
import adminGardenController from './js/controllers/admin_garden.js';
import adminGardenViewController from './js/controllers/admin_garden_view.js';
+import {
+ gardenConfigImportController,
+ gardenConfigImportModalController,
+} from './js/controllers/garden/import_garden_config';
+import gardenConfigExportController from './js/controllers/garden/export_garden_config';
import commandIndexController from './js/controllers/command_index.js';
import commandViewController from './js/controllers/command_view.js';
import requestIndexController from './js/controllers/request_index.js';
@@ -210,7 +215,7 @@ angular
.controller('AdminQueueController', adminQueueController)
.controller(
'AdminSystemForceDeleteController',
- adminSystemForceDeleteController
+ adminSystemForceDeleteController,
)
.controller('AdminSystemController', adminSystemController)
.controller('AdminSystemLogsController', adminSystemLogsController)
@@ -220,6 +225,9 @@ angular
.controller('NewRoleController', newRoleController)
.controller('AdminGardenController', adminGardenController)
.controller('AdminGardenViewController', adminGardenViewController)
+ .controller('GardenConfigExportController', gardenConfigExportController)
+ .controller('GardenConfigImportController', gardenConfigImportController)
+ .controller('GardenConfigImportModalController', gardenConfigImportModalController)
.controller('CommandIndexController', commandIndexController)
.controller('CommandViewController', commandViewController)
.controller('RequestIndexController', requestIndexController)
diff --git a/src/ui/src/js/controllers/admin_garden_view.js b/src/ui/src/js/controllers/admin_garden_view.js
index e1be63ce5..5a261e65d 100644
--- a/src/ui/src/js/controllers/admin_garden_view.js
+++ b/src/ui/src/js/controllers/admin_garden_view.js
@@ -43,8 +43,21 @@ export default function adminGardenViewController(
$scope.$broadcast('schemaFormRedraw');
};
+ $scope.updateModelFromImport = (newGardenDefinition) => {
+ const existingData = $scope.data;
+ const newConnType = newGardenDefinition['connection_type'];
+ const newConnParams = newGardenDefinition['connection_params'];
+
+ const newData = {...existingData,
+ connection_type: newConnType,
+ connection_params: newConnParams};
+
+ $scope.data = newData;
+
+ generateGardenSF();
+ };
+
$scope.successCallback = function(response) {
- console.log('response', response);
$scope.response = response;
$scope.data = response.data;
@@ -121,8 +134,11 @@ export default function adminGardenViewController(
(entryPoint, fieldName) => `schemaForm.error.${entryPoint}.${fieldName}`;
const fieldErrorMessage =
- (errorObject) =>
- typeof errorObject === 'string' ? Array(errorObject) : errorObject;
+ (errorObject) => {
+ const error = typeof errorObject === 'string' ?
+ Array(errorObject) : errorObject;
+ return error[0];
+ };
const updateValidationMessages = (entryPoint, errorsObject) => {
for (const fieldName in errorsObject) {
@@ -144,8 +160,8 @@ export default function adminGardenViewController(
*/
if (response['data'] && response['data']['message']) {
const messageData = String(response['data']['message']);
- console.log('MESSG', messageData, 'TYPE', typeof messageData);
- const cleanedMessages = messageData.replace(/'/g, '"');
+ const singleQuoteRegExp = new RegExp('\'', 'g');
+ const cleanedMessages = messageData.replace(singleQuoteRegExp, '"');
const messages = JSON.parse(cleanedMessages);
if ('connection_params' in messages) {
@@ -171,8 +187,14 @@ export default function adminGardenViewController(
}
};
+ const clearScopeAlerts = () => {
+ while ($scope.alerts.length) {
+ $scope.alerts.pop();
+ }
+ };
+
$scope.submitGardenForm = function(form, model) {
- $scope.alerts = [];
+ clearScopeAlerts();
resetAllValidationErrors();
$scope.$broadcast('schemaFormValidate');
@@ -183,6 +205,8 @@ export default function adminGardenViewController(
updatedGarden =
GardenService.formToServerModel($scope.data, model);
} catch (e) {
+ console.log(e);
+
$scope.alerts.push({
type: 'warning',
msg: e,
@@ -191,7 +215,7 @@ export default function adminGardenViewController(
if (updatedGarden) {
GardenService.updateGardenConfig(updatedGarden).then(
- _.noop,
+ () => console.log('Garden update saved successfully'),
$scope.addErrorAlert,
);
}
diff --git a/src/ui/src/js/controllers/garden/export_garden_config.js b/src/ui/src/js/controllers/garden/export_garden_config.js
new file mode 100644
index 000000000..dcf54ed9f
--- /dev/null
+++ b/src/ui/src/js/controllers/garden/export_garden_config.js
@@ -0,0 +1,51 @@
+gardenConfigExportController.$inject =
+ ['$scope', '$rootScope', '$filter', 'GardenService'];
+
+/**
+ * gardenConfigExportController - Controller for the garden config export page.
+ * @param {Object} $scope Angular's $scope object.
+ * @param {Object} $rootScope Angular's $rootScope object.
+ * @param {Object} $filter Filter
+ * @param {Object} GardenService Beer-Garden's garden service.
+ */
+export default function gardenConfigExportController(
+ $scope,
+ $rootScope,
+ $filter,
+ GardenService,
+) {
+ $scope.response = $rootScope.sysResponse;
+
+ $scope.exportGardenConfig = (gardenName) => {
+ const filename =
+ `GardenExport_${gardenName}_` +
+ $filter('date')(new Date(Date.now()), 'yyyyMMdd_HHmmss');
+
+ const formModel = GardenService.formToServerModel($scope.data, $scope.gardenModel);
+
+ const [newConnectionInfo, newConnectionParams] = [{}, {}];
+ newConnectionInfo['connection_type'] = formModel['connection_type'];
+
+ if (formModel['connection_params']['http']) {
+ newConnectionParams['http'] = formModel['connection_params']['http'];
+ }
+
+ if (formModel['connection_params']['stomp']) {
+ newConnectionParams['stomp'] = formModel['connection_params']['stomp'];
+ }
+
+ newConnectionInfo['connection_params'] = newConnectionParams;
+
+ const blob = new Blob(
+ [JSON.stringify(newConnectionInfo)],
+ {
+ type: 'application/json;charset=utf-8',
+ },
+ );
+ const downloadLink = angular.element('');
+
+ downloadLink.attr('href', window.URL.createObjectURL(blob));
+ downloadLink.attr('download', filename);
+ downloadLink[0].click();
+ };
+}
diff --git a/src/ui/src/js/controllers/garden/import_garden_config.js b/src/ui/src/js/controllers/garden/import_garden_config.js
new file mode 100644
index 000000000..a26d204fc
--- /dev/null
+++ b/src/ui/src/js/controllers/garden/import_garden_config.js
@@ -0,0 +1,101 @@
+import template from '../../../templates/import_garden_config.html';
+
+gardenConfigImportController.$inject = [
+ '$scope',
+ '$rootScope',
+ '$uibModal',
+ '$state',
+];
+
+/**
+ * gardenConfigImportController - Controller for garden config import.
+ * @param {Object} $scope Angular's $scope object.
+ * @param {Object} $rootScope Angular's $rootScope object.
+ * @param {Object} $uibModal Angular UI's $uibModal object.
+ * @param {Object} $state State
+ */
+export function gardenConfigImportController(
+ $scope,
+ $rootScope,
+ $uibModal,
+ $state,
+) {
+ $scope.response = $rootScope.sysResponse;
+
+ $scope.openImportGardenConfigPopup = () => {
+ const popupInstance = $uibModal.open({
+ animation: true,
+ controller: 'GardenConfigImportModalController',
+ template: template,
+ });
+
+ popupInstance.result.then((resolvedResponse) => {
+ const jsonFileContents = resolvedResponse.jsonFileContents;
+ const newConfig = JSON.parse(jsonFileContents);
+
+ $scope.updateModelFromImport(newConfig);
+ }, angular.noop);
+ };
+}
+
+gardenConfigImportModalController.$inject =
+ ['$scope', '$window', '$uibModalInstance'];
+
+/**
+ * gardenConfigImportModalController - Controller for the garden config import
+ * popup window.
+ *
+ * @param {Object} $scope Angular's $scope object.
+ * @param {Object} $window Object for the browser window.
+ * @param {Object} $uibModalInstance Object for the modal popup window.
+ */
+export function gardenConfigImportModalController(
+ $scope, $window, $uibModalInstance) {
+ $scope.import = {};
+ $scope.fileName = undefined;
+ $scope.fileContents = undefined;
+ $scope.fileIsGoodJson = true;
+
+ $scope.inputClicker = function() {
+ $window.document.getElementById('fileSelectHiddenControl').click();
+ };
+
+ $scope.doImport = function() {
+ $scope.import['jsonFileContents'] = $scope.fileContents;
+ $uibModalInstance.close($scope.import);
+ };
+
+ $scope.cancelImport = function() {
+ $uibModalInstance.dismiss('cancel');
+ };
+
+ function isParsableJson(string) {
+ try {
+ JSON.parse(string);
+ } catch (e) {
+ return false;
+ }
+ return true;
+ }
+
+ $scope.onFileSelected = function(event) {
+ const theFile = event.target.files[0];
+ $scope.fileName = theFile.name;
+
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ $scope.$apply(function() {
+ const result = reader.result;
+ const isGoodJson = isParsableJson(result);
+
+ if (isGoodJson) {
+ $scope.fileContents = result;
+ $scope.fileIsGoodJson = true;
+ } else {
+ $scope.fileIsGoodJson = false;
+ }
+ });
+ };
+ reader.readAsText(theFile);
+ };
+}
diff --git a/src/ui/src/js/services/garden_service.js b/src/ui/src/js/services/garden_service.js
index 21ff453aa..6df047435 100644
--- a/src/ui/src/js/services/garden_service.js
+++ b/src/ui/src/js/services/garden_service.js
@@ -48,6 +48,19 @@ export default function gardenService($http) {
return $http.delete('api/v1/gardens/' + encodeURIComponent(name));
};
+ GardenService.importGardenConfig = (gardenDefinition, gardenConfigJson) => {
+ const gardenName = encodeURIComponent(gardenDefinition['name']);
+ const url = `api/v1/gardens/${gardenName}`;
+ const gardenConfig = JSON.parse(gardenConfigJson);
+ gardenDefinition['connection_params'] = gardenConfig;
+
+ return $http.patch(url, {
+ operation: 'config',
+ path: '',
+ value: gardenDefinition,
+ });
+ };
+
GardenService.serverModelToForm = function(model) {
const values = {};
const stompHeaders = [];
@@ -84,72 +97,137 @@ export default function gardenService($http) {
return values;
};
- const isEmptyConnection = (entryPointName, entryPointValues) => {
- const simpleFieldMissing = (entry) => {
- // it's better to be explicit because of the inherent stupidity of
- // Javascript "truthiness"
- return typeof entryPointValues[entry] === 'undefined' ||
+ const getSimpleFieldPredicate = (entryPointValues) => {
+ // it's better to be explicit because of the inherent stupidity of
+ // Javascript "truthiness"
+ return (
+ (entry) =>
+ typeof entryPointValues[entry] === 'undefined' ||
entryPointValues[entry] === null ||
- entryPointValues[entry] === '';
- };
-
- if (entryPointName === 'stomp') {
- const stompSimpleFields = [
- 'host', 'password', 'port', 'send_destination', 'subscribe_destination',
- 'username',
- ];
- const stompSslFields = ['ca_cert', 'client_cert', 'client_key'];
- let nestedFieldsMissing = true;
+ entryPointValues[entry] === ''
+ );
+ };
- const allSimpleFieldsMissing = stompSimpleFields.every(simpleFieldMissing);
+ const isEmptyStompHeaders = (headerEntry) => {
+ const headersExist = !!headerEntry && 'headers' in headerEntry;
+ const headersZeroLength = (
+ !headersExist ||
+ headerEntry['headers'].length === 0);
+ const headersAllEmpty = (
+ !headersZeroLength &&
+ headerEntry['headers'].every((entry) => !!Object.entries(entry))
+ );
+ const headersAllBlank = (
+ !headersAllEmpty &&
+ headerEntry['headers'].every(
+ (entry) =>
+ ('key' in entry && entry['key'] === {}) ||
+ ('value' in entry && entry['value'] == {}))
+ );
+
+ return (
+ !headersExist ||
+ headersZeroLength ||
+ headersAllEmpty ||
+ headersAllBlank
+ );
+ };
- const sslIsMissing = typeof entryPointValues['ssl'] === 'undefined' ||
- entryPointValues['ssl'] === {};
+ const cleanEmptyStompHeaders = 'TODO';
+
+ const isEmptyStompConnection = (entryPointValues) => {
+ /* If every field is missing, then obviously the stomp connection can be
+ * considered empty.
+ *
+ * It gets a little more complicated in other cases because a lot of garbage
+ * data is being passed around (an issue for another day).
+ *
+ * So we do a lot of checking of the corner cases in
+ * isEmptyStompConnection and isEmptyStompHeaders so that the results of
+ * this function is truly representative of whether we would consider the
+ * connection to be "empty".
+ *
+ * (The point of all this is that if the connection meets our common-sense
+ * definition of empty, then the resulting connection parameter object
+ * won't even have a 'stomp' entry at all, which is far preferable to
+ * polluting the database with the cruft that gets picked up in the UI.)
+ */
+ const simpleFieldMissing = getSimpleFieldPredicate(entryPointValues);
- if (!sslIsMissing) {
- nestedFieldsMissing = stompSslFields.every(
- (entry) =>
- typeof entryPointValues['ssl'][entry] == 'undefined' ||
- entryPointValues['ssl'][entry] == null ||
+ const stompSimpleFields = [
+ 'host', 'password', 'port', 'send_destination', 'subscribe_destination',
+ 'username',
+ ];
+ const stompSslFields = ['ca_cert', 'client_cert', 'client_key'];
+
+ const allSimpleFieldsMissing = stompSimpleFields.every(simpleFieldMissing);
+ const headersMissing = isEmptyStompHeaders(entryPointValues);
+ const sslIsMissing = typeof entryPointValues['ssl'] === 'undefined' ||
+ !!Object.entries(entryPointValues['ssl']);
+ let nestedFieldsMissing = true;
+
+ if (!sslIsMissing) {
+ nestedFieldsMissing = stompSslFields.every(
+ (entry) =>
+ typeof entryPointValues['ssl'][entry] === 'undefined' ||
+ entryPointValues['ssl'][entry] === null ||
entryPointValues['ssl'][entry] === '',
- );
- }
+ );
+ }
- return entryPointValues['headers'].length === 0 &&
- allSimpleFieldsMissing &&
+ const allStompFieldsEmpty = headersMissing && allSimpleFieldsMissing &&
nestedFieldsMissing;
- }
- // is 'http'
+ return allStompFieldsEmpty;
+ };
+
+ const isEmptyHttpConnection = (entryPointValues) => {
+ // Simply decide if every field in the http entry is blank.
+ const simpleFieldMissing = getSimpleFieldPredicate(entryPointValues);
const httpSimpleFields = [
'ca_cert', 'client_cert', 'host', 'port', 'url_prefix',
];
+ const allHttpFieldsEmpty = httpSimpleFields.every(simpleFieldMissing);
- return httpSimpleFields.every(simpleFieldMissing);
+ return allHttpFieldsEmpty;
};
- GardenService.formToServerModel = function(model, form) {
+ GardenService.formToServerModel = function(data, model) {
/* Carefully pick apart the form data and translate it to the correct server
- * model. Throw an error if the entire form is empty (i.e., cannot have
- * empty connection parameters for both entry points).
+ * model. Throw an error if the entire form is empty (i.e., don't allow
+ * empty connection parameters for both entry points on a remote garden).
*/
- const {connection_type: formConnectionType, ...formWithoutConxType} = form;
- model['connection_type'] = formConnectionType;
+ const {connection_type: modelConnectionType, ...modelWithoutConxType} = model;
+ let newModel = {...data};
+ newModel['connection_type'] = modelConnectionType;
- const modelUpdatedConnectionParams = {};
+ const updatedConnectionParams = {};
const emptyConnections = {};
+ const emptyChecker = {
+ 'stomp': isEmptyStompConnection,
+ 'http': isEmptyHttpConnection,
+ };
+
+ for (const modelEntryPointName of Object.keys(modelWithoutConxType)) {
+ // modelEntryPointName is either 'http' or 'stomp'
+ const modelEntryPointMap = modelWithoutConxType[modelEntryPointName];
+ const isEmpty = emptyChecker[modelEntryPointName](modelEntryPointMap);
- for (const formEntryPointName of Object.keys(formWithoutConxType)) {
- // formEntryPointName is either 'http' or 'stomp'
- const formEntryPointMap = formWithoutConxType[formEntryPointName];
- const modelUpdatedEntryPoint = {};
+ if (isEmpty) {
+ emptyConnections[modelEntryPointName] = true;
+ continue;
+ } else {
+ emptyConnections[modelEntryPointName] = false;
+ }
+
+ const updatedEntryPoint = {};
- for (const formEntryPointKey of Object.keys(formEntryPointMap)) {
- const formEntryPointValue = formEntryPointMap[formEntryPointKey];
+ for (const modelEntryPointKey of Object.keys(modelEntryPointMap)) {
+ const modelEntryPointValue = modelEntryPointMap[modelEntryPointKey];
- if (formEntryPointName === 'stomp' && formEntryPointKey === 'headers') {
+ if (modelEntryPointName === 'stomp' && modelEntryPointKey === 'headers') {
// the ugly corner case is the stomp headers
- const formStompHeaders = formEntryPointValue;
+ const formStompHeaders = modelEntryPointValue;
const modelUpdatedStompHeaderArray = [];
for (const formStompHeader of formStompHeaders) {
@@ -167,26 +245,29 @@ export default function gardenService($http) {
}
}
- modelUpdatedEntryPoint['headers'] = modelUpdatedStompHeaderArray;
+ if (modelUpdatedStompHeaderArray.length > 0) {
+ updatedEntryPoint['headers'] = modelUpdatedStompHeaderArray;
+ }
} else {
- modelUpdatedEntryPoint[formEntryPointKey] = formEntryPointValue;
+ updatedEntryPoint[modelEntryPointKey] = modelEntryPointValue;
}
}
- if (!isEmptyConnection(formEntryPointName, modelUpdatedEntryPoint)) {
- modelUpdatedConnectionParams[formEntryPointName] =
- modelUpdatedEntryPoint;
- } else {
- emptyConnections[formEntryPointName] = true;
- }
+ updatedConnectionParams[modelEntryPointName] = updatedEntryPoint;
}
if (emptyConnections['http'] && emptyConnections['stomp']) {
throw Error('One of \'http\' or \'stomp\' connection must be defined');
+ } else if (emptyConnections['http'] && modelConnectionType === 'HTTP') {
+ throw Error('Connection type is \'HTTP\' but http connection parameters' +
+ 'are blank');
+ } else if (emptyConnections['stomp'] && modelConnectionType === 'STOMP') {
+ throw Error('Connection type is \'STOMP\' but stomp connection ' +
+ 'parameters are blank');
}
- model = {...model, 'connection_params': modelUpdatedConnectionParams};
+ newModel = {...newModel, 'connection_params': updatedConnectionParams};
- return model;
+ return newModel;
};
GardenService.CONNECTION_TYPES = ['HTTP', 'STOMP'];
diff --git a/src/ui/src/partials/admin_garden_view.html b/src/ui/src/partials/admin_garden_view.html
index dd78558ca..0e626c702 100644
--- a/src/ui/src/partials/admin_garden_view.html
+++ b/src/ui/src/partials/admin_garden_view.html
@@ -74,15 +74,16 @@
Update Connection
+ ng-controller="GardenConfigImportController"
+ ng-click="openImportGardenConfigPopup()">
Import Connection
+ ng-controller="GardenConfigExportController"
+ ng-click="exportGardenConfig(data.name)">
Export Connection
diff --git a/src/ui/src/templates/import_garden_config.html b/src/ui/src/templates/import_garden_config.html
new file mode 100644
index 000000000..3d13c131e
--- /dev/null
+++ b/src/ui/src/templates/import_garden_config.html
@@ -0,0 +1,34 @@
+
From 8c9131d77efbe5a4fab0b7230c03215115bc6660 Mon Sep 17 00:00:00 2001
From: John B
Date: Tue, 18 Jan 2022 09:17:28 -0500
Subject: [PATCH 7/7] Fixed remaining eslint errors
---
src/ui/src/js/configs/compiler_config.js | 2 +-
src/ui/src/js/configs/dt_renderer.js | 18 +++++++++---------
src/ui/src/js/configs/http_interceptor.js | 4 ++--
src/ui/src/js/controllers/about.js | 2 +-
src/ui/src/js/controllers/admin_queue.js | 6 +++---
src/ui/src/js/controllers/admin_role.js | 18 +++++++++---------
src/ui/src/js/controllers/admin_system.js | 4 ++--
.../controllers/admin_system_force_delete.js | 2 +-
src/ui/src/js/controllers/admin_system_logs.js | 8 ++++----
src/ui/src/js/controllers/admin_user.js | 12 ++++++------
src/ui/src/js/controllers/command_index.js | 6 +++---
src/ui/src/js/controllers/command_view.js | 14 +++++++-------
.../src/js/controllers/job/create_request.js | 8 ++++----
.../src/js/controllers/job/create_trigger.js | 12 ++++++------
src/ui/src/js/controllers/job/export_jobs.js | 4 ++--
src/ui/src/js/controllers/job/import_jobs.js | 4 ++--
src/ui/src/js/controllers/job_view.js | 18 +++++++++---------
src/ui/src/js/controllers/login.js | 4 ++--
src/ui/src/js/controllers/request_index.js | 6 +++---
src/ui/src/js/controllers/request_view.js | 8 ++++----
src/ui/src/js/controllers/system_index.js | 4 ++--
src/ui/src/js/run.js | 6 +++---
src/ui/src/js/services/error_service.js | 2 +-
src/ui/src/js/services/request_service.js | 4 ++--
src/ui/src/js/services/role_service.js | 8 ++++----
src/ui/src/js/services/system_service.js | 2 +-
src/ui/src/js/services/token_service.js | 2 +-
src/ui/src/js/services/user_service.js | 4 ++--
src/ui/src/js/services/utility_service.js | 2 +-
29 files changed, 97 insertions(+), 97 deletions(-)
diff --git a/src/ui/src/js/configs/compiler_config.js b/src/ui/src/js/configs/compiler_config.js
index 456c8cc91..a80aa1f48 100644
--- a/src/ui/src/js/configs/compiler_config.js
+++ b/src/ui/src/js/configs/compiler_config.js
@@ -5,6 +5,6 @@ compilerConfig.$inject = ['$compileProvider'];
*/
export function compilerConfig($compileProvider) {
$compileProvider.aHrefSanitizationTrustedUrlList(
- /^\s*(https?|ftp|mailto|tel|file|blob):/
+ /^\s*(https?|ftp|mailto|tel|file|blob):/,
);
}
diff --git a/src/ui/src/js/configs/dt_renderer.js b/src/ui/src/js/configs/dt_renderer.js
index 86847a222..d4184c84a 100644
--- a/src/ui/src/js/configs/dt_renderer.js
+++ b/src/ui/src/js/configs/dt_renderer.js
@@ -18,13 +18,13 @@ export default function runDTRenderer(DTRendererService) {
.css('margin-top', '-4px')
.change(() => {
$('.dataTable').dataTable().fnUpdate();
- })
+ }),
)
.append(
$('')
.attr('for', 'childCheck')
.css('padding-left', '4px')
- .text('Show Children')
+ .text('Show Children'),
);
$('.dataTables_filter').prepend(childContainer);
}
@@ -40,13 +40,13 @@ export default function runDTRenderer(DTRendererService) {
.css('margin-top', '-4px')
.change(() => {
$('.dataTable').dataTable().fnUpdate();
- })
+ }),
)
.append(
$('')
.attr('for', 'hiddenRequestCheck')
.css('padding-left', '4px')
- .text('Show Hidden')
+ .text('Show Hidden'),
);
$('.dataTables_filter').prepend(hiddenRequestContainer);
}
@@ -57,13 +57,13 @@ export default function runDTRenderer(DTRendererService) {
.css('margin-right', '20px')
.attr(
'style',
- 'list-style-type:none;margin-right:20px; display:inline; padding-left:0px;'
+ 'list-style-type:none;margin-right:20px; display:inline; padding-left:0px;',
)
.append(
$('')
.attr('for', 'filterHidden')
.css('padding-left', '4px')
- .text('Show Hidden')
+ .text('Show Hidden'),
);
$('.dataTables_filter').prepend(hiddenContainer);
const node = document.getElementById('filterHidden');
@@ -87,7 +87,7 @@ export default function runDTRenderer(DTRendererService) {
$('')
.addClass('fa')
.addClass('fa-refresh')
- .css('padding-right', '5px')
+ .css('padding-right', '5px'),
)
.append($('').text('Refresh'));
$('.dataTables_length').append(refreshButton);
@@ -103,10 +103,10 @@ export default function runDTRenderer(DTRendererService) {
$('')
.addClass('glyphicon')
.addClass('glyphicon-info-sign')
- .css('padding-right', '5px')
+ .css('padding-right', '5px'),
)
.append(
- $('').css('cursor', 'default').text('Updates Detected')
+ $('').css('cursor', 'default').text('Updates Detected'),
);
$('.dataTables_length').append(newData);
}
diff --git a/src/ui/src/js/configs/http_interceptor.js b/src/ui/src/js/configs/http_interceptor.js
index 3663de5cb..4f95a0b06 100644
--- a/src/ui/src/js/configs/http_interceptor.js
+++ b/src/ui/src/js/configs/http_interceptor.js
@@ -66,7 +66,7 @@ export function authInterceptorService($q, $injector) {
},
(response) => {
deferred.reject();
- }
+ },
);
});
@@ -91,7 +91,7 @@ export function authInterceptorService($q, $injector) {
() => {
// User dismissed the modal so return the original rejection
return $q.reject(rejection);
- }
+ },
);
}
}
diff --git a/src/ui/src/js/controllers/about.js b/src/ui/src/js/controllers/about.js
index 1a5ae93ca..c7d9bd78f 100644
--- a/src/ui/src/js/controllers/about.js
+++ b/src/ui/src/js/controllers/about.js
@@ -15,6 +15,6 @@ export default function AboutController($scope, UtilityService) {
},
(response) => {
$scope.response = response;
- }
+ },
);
}
diff --git a/src/ui/src/js/controllers/admin_queue.js b/src/ui/src/js/controllers/admin_queue.js
index 7aafb0821..18652e93d 100644
--- a/src/ui/src/js/controllers/admin_queue.js
+++ b/src/ui/src/js/controllers/admin_queue.js
@@ -24,7 +24,7 @@ export default function adminQueueController(
$interval,
QueueService,
system,
- instance
+ instance,
) {
$scope.alerts = [];
$scope.system = system;
@@ -34,7 +34,7 @@ export default function adminQueueController(
$scope.clearQueue = function(queueName) {
QueueService.clearQueue(queueName).then(
$scope.addSuccessAlert,
- $scope.failureCallback
+ $scope.failureCallback,
);
};
@@ -95,7 +95,7 @@ export default function adminQueueController(
$scope.queues = QueueService.getInstanceQueues($scope.instance.id).then(
$scope.successCallback,
- $scope.addErrorAlert
+ $scope.addErrorAlert,
);
}
diff --git a/src/ui/src/js/controllers/admin_role.js b/src/ui/src/js/controllers/admin_role.js
index a293f487d..acc538a2b 100644
--- a/src/ui/src/js/controllers/admin_role.js
+++ b/src/ui/src/js/controllers/admin_role.js
@@ -24,7 +24,7 @@ export function adminRoleController(
$q,
$uibModal,
RoleService,
- PermissionService
+ PermissionService,
) {
$scope.setWindowTitle('roles');
@@ -55,7 +55,7 @@ export function adminRoleController(
RoleService.createRole(create).then(loadAll);
},
// We don't really need to do anything if canceled
- () => {}
+ () => {},
);
};
@@ -180,7 +180,7 @@ export function adminRoleController(
$scope.raws.roles,
(value, key, collection) => {
return _.indexOf(primaryRoleNames, value.name) !== -1;
- }
+ },
);
// ...so that we can calculate nested permissions...
@@ -190,7 +190,7 @@ export function adminRoleController(
// And then combine them into one big list o' permissions
const allPermissionNames = _.union(
primaryPermissionNames,
- nestedPermissionNames
+ nestedPermissionNames,
);
// Finally, convert that list back into the map angular wants
@@ -273,25 +273,25 @@ export function adminRoleController(
const nestedRoleNames = _.difference(allRoleNames, primaryRoleNames);
const allPermissionNames = _.union(
primaryPermissionNames,
- coalesced[1]
+ coalesced[1],
);
const roleMap = arrayToMap(allRoleNames, $scope.roleNames);
const permissionMap = arrayToMap(
allPermissionNames,
- $scope.raws.permissions
+ $scope.raws.permissions,
);
const primaryRoleMap = arrayToMap(primaryRoleNames, $scope.roleNames);
const primaryPermissionMap = arrayToMap(
primaryPermissionNames,
- $scope.raws.permissions
+ $scope.raws.permissions,
);
const nestedRoleMap = arrayToMap(nestedRoleNames, $scope.roleNames);
const nestedPermissionMap = arrayToMap(
nestedPermissionNames,
- $scope.raws.permissions
+ $scope.raws.permissions,
);
thaRoles.push({
@@ -331,7 +331,7 @@ export function adminRoleController(
},
(response) => {
$scope.response = response;
- }
+ },
);
}
diff --git a/src/ui/src/js/controllers/admin_system.js b/src/ui/src/js/controllers/admin_system.js
index 65494303d..6e2cdfec3 100644
--- a/src/ui/src/js/controllers/admin_system.js
+++ b/src/ui/src/js/controllers/admin_system.js
@@ -40,7 +40,7 @@ export default function adminSystemController(
AdminService,
QueueService,
RunnerService,
- EventService
+ EventService,
) {
$scope.response = undefined;
$scope.runnerResponse = undefined;
@@ -88,7 +88,7 @@ export default function adminSystemController(
$scope.clearAllQueues = function() {
QueueService.clearQueues().then(
$scope.addSuccessAlert,
- $scope.addErrorAlert
+ $scope.addErrorAlert,
);
};
diff --git a/src/ui/src/js/controllers/admin_system_force_delete.js b/src/ui/src/js/controllers/admin_system_force_delete.js
index 87de6f392..08eb15c0d 100644
--- a/src/ui/src/js/controllers/admin_system_force_delete.js
+++ b/src/ui/src/js/controllers/admin_system_force_delete.js
@@ -22,7 +22,7 @@ export default function adminSystemForceDeleteController(
$uibModalInstance,
SystemService,
system,
- response
+ response,
) {
$scope.alerts = [];
$scope.system = system;
diff --git a/src/ui/src/js/controllers/admin_system_logs.js b/src/ui/src/js/controllers/admin_system_logs.js
index d1b82dac7..fc3b0f5dd 100644
--- a/src/ui/src/js/controllers/admin_system_logs.js
+++ b/src/ui/src/js/controllers/admin_system_logs.js
@@ -19,7 +19,7 @@ export default function adminSystemLogsController(
$uibModalInstance,
InstanceService,
system,
- instance
+ instance,
) {
$scope.logs = undefined;
$scope.start_line = 0;
@@ -70,7 +70,7 @@ export default function adminSystemLogsController(
instance.id,
$scope.wait_timeout,
$scope.start_line,
- $scope.end_line
+ $scope.end_line,
).then($scope.successLogs, $scope.addErrorAlert);
};
@@ -82,7 +82,7 @@ export default function adminSystemLogsController(
instance.id,
$scope.wait_timeout,
$scope.tail_line * -1,
- null
+ null,
).then($scope.successLogs, $scope.addErrorAlert);
};
@@ -94,7 +94,7 @@ export default function adminSystemLogsController(
instance.id,
$scope.wait_timeout,
null,
- null
+ null,
).then($scope.successLogs, $scope.addErrorAlert);
};
diff --git a/src/ui/src/js/controllers/admin_user.js b/src/ui/src/js/controllers/admin_user.js
index 78f6d48e6..9f43ad3ea 100644
--- a/src/ui/src/js/controllers/admin_user.js
+++ b/src/ui/src/js/controllers/admin_user.js
@@ -27,7 +27,7 @@ export function adminUserController(
$uibModal,
RoleService,
UserService,
- PermissionService
+ PermissionService,
) {
$scope.setWindowTitle('users');
@@ -57,12 +57,12 @@ export function adminUserController(
(create) => {
if (create.password === create.verify) {
UserService.createUser(create.username, create.password).then(
- loadUsers
+ loadUsers,
);
}
},
// We don't really need to do anything if canceled
- () => {}
+ () => {},
);
};
@@ -152,7 +152,7 @@ export function adminUserController(
$scope.raws.roles,
(value, key, collection) => {
return _.indexOf(primaryRoleNames, value.name) !== -1;
- }
+ },
);
// ...so that we can calculate nested permissions...
@@ -196,7 +196,7 @@ export function adminUserController(
const roleMap = arrayToMap(allRoleNames, $scope.roleNames);
const permissionMap = arrayToMap(
allPermissionNames,
- $scope.raws.permissions
+ $scope.raws.permissions,
);
const primaryRoleMap = arrayToMap(primaryRoleNames, $scope.roleNames);
@@ -270,7 +270,7 @@ export function adminUserController(
},
(response) => {
$scope.response = response;
- }
+ },
);
}
diff --git a/src/ui/src/js/controllers/command_index.js b/src/ui/src/js/controllers/command_index.js
index ccd291935..4dd40635f 100644
--- a/src/ui/src/js/controllers/command_index.js
+++ b/src/ui/src/js/controllers/command_index.js
@@ -25,7 +25,7 @@ export default function commandIndexController(
$sce,
localStorageService,
DTOptionsBuilder,
- SystemService
+ SystemService,
) {
$scope.setWindowTitle('commands');
$scope.filterHidden = false;
@@ -35,7 +35,7 @@ export default function commandIndexController(
.withOption('autoWidth', false)
.withOption(
'pageLength',
- localStorageService.get('_command_index_length') || 10
+ localStorageService.get('_command_index_length') || 10,
)
.withOption('hiddenContainer', true)
.withOption('order', [
@@ -140,7 +140,7 @@ export default function commandIndexController(
const foundSystem = SystemService.findSystem(
$stateParams.namespace,
$stateParams.systemName,
- $stateParams.systemVersion
+ $stateParams.systemVersion,
);
if (foundSystem.template) {
if ($scope.config.executeJavascript) {
diff --git a/src/ui/src/js/controllers/command_view.js b/src/ui/src/js/controllers/command_view.js
index e16cb4a37..b99c2734e 100644
--- a/src/ui/src/js/controllers/command_view.js
+++ b/src/ui/src/js/controllers/command_view.js
@@ -37,7 +37,7 @@ export default function commandViewController(
RequestService,
SFBuilderService,
system,
- command
+ command,
) {
let tempResponse = $rootScope.sysResponse;
@@ -131,7 +131,7 @@ export default function commandViewController(
$scope.createRequest(model);
} else {
$scope.alerts.push(
- 'Looks like there was an error validating the request.'
+ 'Looks like there was an error validating the request.',
);
}
};
@@ -167,7 +167,7 @@ export default function commandViewController(
}
fd.append(
$scope.command.parameters[i].key,
- document.getElementById($scope.command.parameters[i].key).files[0]
+ document.getElementById($scope.command.parameters[i].key).files[0],
);
}
}
@@ -182,7 +182,7 @@ export default function commandViewController(
},
function(response) {
$scope.createResponse = response;
- }
+ },
);
};
@@ -257,7 +257,7 @@ export default function commandViewController(
$scope.command.name,
$scope.system.display_name || $scope.system.name,
$scope.system.version,
- 'command'
+ 'command',
);
};
@@ -283,7 +283,7 @@ export default function commandViewController(
}
}
},
- true
+ true,
);
// This process of stringify / parse will break the optional model
@@ -309,7 +309,7 @@ export default function commandViewController(
}
}
},
- true
+ true,
);
}
diff --git a/src/ui/src/js/controllers/job/create_request.js b/src/ui/src/js/controllers/job/create_request.js
index dc84e41dc..f20b6a845 100644
--- a/src/ui/src/js/controllers/job/create_request.js
+++ b/src/ui/src/js/controllers/job/create_request.js
@@ -21,7 +21,7 @@ export default function jobCreateRequestController(
$state,
$stateParams,
SFBuilderService,
- SystemService
+ SystemService,
) {
$scope.setWindowTitle('scheduler');
@@ -39,7 +39,7 @@ export default function jobCreateRequestController(
$scope.system = SystemService.findSystem(
$stateParams.job.request_template.namespace,
$stateParams.job.request_template.system,
- $stateParams.job.request_template.system_version
+ $stateParams.job.request_template.system_version,
);
for (const i in $scope.system.commands) {
@@ -91,7 +91,7 @@ export default function jobCreateRequestController(
}
}
},
- true
+ true,
);
$scope.submit = function(form, model) {
@@ -136,7 +136,7 @@ export default function jobCreateRequestController(
});
} else {
$scope.alerts.push(
- 'Looks like there was an error validating the request.'
+ 'Looks like there was an error validating the request.',
);
}
};
diff --git a/src/ui/src/js/controllers/job/create_trigger.js b/src/ui/src/js/controllers/job/create_trigger.js
index 254bc6cf3..8317a048a 100644
--- a/src/ui/src/js/controllers/job/create_trigger.js
+++ b/src/ui/src/js/controllers/job/create_trigger.js
@@ -18,7 +18,7 @@ export default function jobCreateTriggerController(
$scope,
$state,
$stateParams,
- JobService
+ JobService,
) {
$scope.setWindowTitle('scheduler');
@@ -62,7 +62,7 @@ export default function jobCreateTriggerController(
model['interval_timezone'] === null)
) {
$scope.alerts.push(
- 'If a date is specified, you must specify the timezone.'
+ 'If a date is specified, you must specify the timezone.',
);
valid = false;
}
@@ -78,7 +78,7 @@ export default function jobCreateTriggerController(
model['cron_timezone'] === null)
) {
$scope.alerts.push(
- 'If a date is specified, you must specify the timezone.'
+ 'If a date is specified, you must specify the timezone.',
);
valid = false;
}
@@ -129,7 +129,7 @@ export default function jobCreateTriggerController(
}
}
},
- true
+ true,
);
$scope.submit = function(form, model) {
@@ -148,11 +148,11 @@ export default function jobCreateTriggerController(
},
function(response) {
$scope.createResponse = response;
- }
+ },
);
} else {
$scope.alerts.push(
- 'Looks like there was an error validating the request.'
+ 'Looks like there was an error validating the request.',
);
}
};
diff --git a/src/ui/src/js/controllers/job/export_jobs.js b/src/ui/src/js/controllers/job/export_jobs.js
index e9122e71a..bf5004597 100644
--- a/src/ui/src/js/controllers/job/export_jobs.js
+++ b/src/ui/src/js/controllers/job/export_jobs.js
@@ -11,7 +11,7 @@ export default function jobExportController(
$scope,
$rootScope,
$filter,
- JobService
+ JobService,
) {
$scope.response = $rootScope.sysResponse;
$scope.data = $rootScope.systems;
@@ -30,7 +30,7 @@ export default function jobExportController(
downloadLink.attr('download', filename);
downloadLink[0].click();
},
- () => alert('Aborting: No Jobs defined or unknown error.')
+ () => alert('Aborting: No Jobs defined or unknown error.'),
);
};
}
diff --git a/src/ui/src/js/controllers/job/import_jobs.js b/src/ui/src/js/controllers/job/import_jobs.js
index 3933e61a2..ea51a0fc2 100644
--- a/src/ui/src/js/controllers/job/import_jobs.js
+++ b/src/ui/src/js/controllers/job/import_jobs.js
@@ -21,7 +21,7 @@ export function jobImportController(
$rootScope,
$uibModal,
$state,
- JobService
+ JobService,
) {
$scope.response = $rootScope.sysResponse;
$scope.data = $rootScope.systems;
@@ -41,7 +41,7 @@ export function jobImportController(
},
function(response) {
alert('Failure! Server returned status ' + response.status);
- }
+ },
);
}, angular.noop);
};
diff --git a/src/ui/src/js/controllers/job_view.js b/src/ui/src/js/controllers/job_view.js
index b8b37a052..e9edade7e 100644
--- a/src/ui/src/js/controllers/job_view.js
+++ b/src/ui/src/js/controllers/job_view.js
@@ -25,7 +25,7 @@ export function jobViewController(
$state,
$stateParams,
$uibModal,
- JobService
+ JobService,
) {
$scope.setWindowTitle('scheduler');
@@ -45,7 +45,7 @@ export function jobViewController(
$scope.formattedRequestTemplate = JSON.stringify(
$scope.data.request_template,
undefined,
- 2
+ 2,
);
$scope.formattedTrigger = JSON.stringify($scope.data.trigger, undefined, 2);
};
@@ -53,14 +53,14 @@ export function jobViewController(
$scope.resumeJob = function(jobId) {
JobService.resumeJob(jobId).then(
$scope.successCallback,
- $scope.failureCallback
+ $scope.failureCallback,
);
};
$scope.pauseJob = function(jobId) {
JobService.pauseJob(jobId).then(
$scope.successCallback,
- $scope.failureCallback
+ $scope.failureCallback,
);
};
@@ -71,7 +71,7 @@ export function jobViewController(
$scope.deleteJob = function(jobId) {
JobService.deleteJob(jobId).then(
$state.go('base.jobs'),
- $scope.failureCallback
+ $scope.failureCallback,
);
};
@@ -86,7 +86,7 @@ export function jobViewController(
JobService.getJob($stateParams.id).then(
$scope.successCallback,
- $scope.failureCallback
+ $scope.failureCallback,
);
}
@@ -103,13 +103,13 @@ export function jobViewController(
JobService.runAdHocJob(jobId, $scope.resetTheInterval).then(
function(response) {
console.log(
- `Ad hoc run of ID ${jobId}; reset interval: ${resettingInterval}`
+ `Ad hoc run of ID ${jobId}; reset interval: ${resettingInterval}`,
);
$state.go('base.jobs');
},
function(response) {
alert('Failure! Server returned status ' + response.status);
- }
+ },
);
}
@@ -146,7 +146,7 @@ export function jobViewController(
$scope.resetTheInterval = result;
runAdHoc(jobId);
},
- () => console.log('Ad hoc run cancelled')
+ () => console.log('Ad hoc run cancelled'),
);
} else {
runAdHoc(jobId);
diff --git a/src/ui/src/js/controllers/login.js b/src/ui/src/js/controllers/login.js
index 1c6e60cb5..bd9cbb403 100644
--- a/src/ui/src/js/controllers/login.js
+++ b/src/ui/src/js/controllers/login.js
@@ -19,7 +19,7 @@ export default function loginController(
$scope,
$timeout,
$uibModalInstance,
- TokenService
+ TokenService,
) {
$scope.model = {};
@@ -40,7 +40,7 @@ export default function loginController(
$scope.model.password = undefined;
angular.element('input[type="password"]').focus();
}
- }
+ },
);
};
diff --git a/src/ui/src/js/controllers/request_index.js b/src/ui/src/js/controllers/request_index.js
index 6b2ecad15..9862c2845 100644
--- a/src/ui/src/js/controllers/request_index.js
+++ b/src/ui/src/js/controllers/request_index.js
@@ -27,7 +27,7 @@ export default function requestIndexController(
DTOptionsBuilder,
DTColumnBuilder,
RequestService,
- EventService
+ EventService,
) {
$scope.setWindowTitle('requests');
@@ -37,7 +37,7 @@ export default function requestIndexController(
.withOption('autoWidth', false)
.withOption(
'pageLength',
- localStorageService.get('_request_index_length') || 10
+ localStorageService.get('_request_index_length') || 10,
)
.withOption('ajax', function(data, callback, settings) {
// Need to also request ID for the href
@@ -76,7 +76,7 @@ export default function requestIndexController(
},
(response) => {
$scope.response = response;
- }
+ },
);
})
.withLightColumnFilter({
diff --git a/src/ui/src/js/controllers/request_view.js b/src/ui/src/js/controllers/request_view.js
index 7169a1a91..1bd5b37c9 100644
--- a/src/ui/src/js/controllers/request_view.js
+++ b/src/ui/src/js/controllers/request_view.js
@@ -38,7 +38,7 @@ export default function requestViewController(
localStorageService,
RequestService,
SystemService,
- EventService
+ EventService,
) {
$scope.request = undefined;
$scope.complete = false;
@@ -184,7 +184,7 @@ export default function requestViewController(
const requestSystem = SystemService.findSystem(
namespace,
$scope.request.system,
- $scope.request.system_version
+ $scope.request.system_version,
);
if (requestSystem != undefined) {
const commands = requestSystem.commands;
@@ -207,7 +207,7 @@ export default function requestViewController(
$scope.request.metadata.system_display_name || $scope.request.system,
$scope.request.system_version,
$scope.request.instance_name,
- 'request'
+ 'request',
);
if (RequestService.isComplete($scope.request)) {
@@ -240,7 +240,7 @@ export default function requestViewController(
const system = SystemService.findSystem(
$scope.request.namespace,
$scope.request.system,
- $scope.request.system_version
+ $scope.request.system_version,
);
$scope.instanceStatus = _.find(system.instances, {
name: $scope.request.instance_name,
diff --git a/src/ui/src/js/controllers/system_index.js b/src/ui/src/js/controllers/system_index.js
index d402b757b..620bf0fd5 100644
--- a/src/ui/src/js/controllers/system_index.js
+++ b/src/ui/src/js/controllers/system_index.js
@@ -19,7 +19,7 @@ export default function systemIndexController(
$rootScope,
localStorageService,
UtilityService,
- DTOptionsBuilder
+ DTOptionsBuilder,
) {
$scope.setWindowTitle();
@@ -29,7 +29,7 @@ export default function systemIndexController(
.withOption('autoWidth', false)
.withOption(
'pageLength',
- localStorageService.get('_system_index_length') || 10
+ localStorageService.get('_system_index_length') || 10,
)
.withOption('order', [
[0, 'asc'],
diff --git a/src/ui/src/js/run.js b/src/ui/src/js/run.js
index a4a9b6bf1..0e670b71c 100644
--- a/src/ui/src/js/run.js
+++ b/src/ui/src/js/run.js
@@ -58,7 +58,7 @@ export default function appRun(
UserService,
TokenService,
RoleService,
- EventService
+ EventService,
) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
@@ -105,7 +105,7 @@ export default function appRun(
},
(response) => {
return $q.reject(response);
- }
+ },
);
return $rootScope.userPromise;
};
@@ -191,7 +191,7 @@ export default function appRun(
$rootScope.changeUser(TokenService.getToken());
location.reload();
},
- _.noop // Prevents annoying console log messages
+ _.noop, // Prevents annoying console log messages
);
loginModal.closed.then(() => {
loginModal = undefined;
diff --git a/src/ui/src/js/services/error_service.js b/src/ui/src/js/services/error_service.js
index 9ca98df3b..ee358a713 100644
--- a/src/ui/src/js/services/error_service.js
+++ b/src/ui/src/js/services/error_service.js
@@ -82,7 +82,7 @@ export default function errorService() {
return _.concat(
_.get(errorMap[statusCode], specific, []),
- _.get(errorMap[statusCode], 'common', [])
+ _.get(errorMap[statusCode], 'common', []),
);
},
};
diff --git a/src/ui/src/js/services/request_service.js b/src/ui/src/js/services/request_service.js
index 32899cbe3..7e374b7ad 100644
--- a/src/ui/src/js/services/request_service.js
+++ b/src/ui/src/js/services/request_service.js
@@ -63,13 +63,13 @@ export default function requestService($q, $http, $interval) {
(errorResponse) => {
deferred.reject(errorResponse.data);
$interval.cancel(inter);
- }
+ },
);
}, 500);
},
(errorResponse) => {
deferred.reject(errorResponse.data);
- }
+ },
);
return deferred.promise;
diff --git a/src/ui/src/js/services/role_service.js b/src/ui/src/js/services/role_service.js
index 5eab78373..f05524c4e 100644
--- a/src/ui/src/js/services/role_service.js
+++ b/src/ui/src/js/services/role_service.js
@@ -57,7 +57,7 @@ export default function roleService($http) {
roleId,
_.map(permissions, (value) => {
return {operation: 'add', path: '/permissions', value: value};
- })
+ }),
);
},
removePermissions: (roleId, permissions) => {
@@ -65,7 +65,7 @@ export default function roleService($http) {
roleId,
_.map(permissions, (value) => {
return {operation: 'remove', path: '/permissions', value: value};
- })
+ }),
);
},
setPermissions: (roleId, permissions) => {
@@ -78,7 +78,7 @@ export default function roleService($http) {
roleId,
_.map(roles, (value) => {
return {operation: 'add', path: '/roles', value: value};
- })
+ }),
);
},
removeRoles: (roleId, roles) => {
@@ -86,7 +86,7 @@ export default function roleService($http) {
roleId,
_.map(roles, (value) => {
return {operation: 'remove', path: '/roles', value: value};
- })
+ }),
);
},
setRoles: (roleId, roles) => {
diff --git a/src/ui/src/js/services/system_service.js b/src/ui/src/js/services/system_service.js
index 977982489..9caf3843a 100644
--- a/src/ui/src/js/services/system_service.js
+++ b/src/ui/src/js/services/system_service.js
@@ -98,7 +98,7 @@ export default function systemService($rootScope, $http) {
// All versions for systems with the given system name
const versions = _.map(
_.filter($rootScope.systems, {name: system.name}),
- _.property('version')
+ _.property('version'),
);
// Sorted according to the system comparison function
diff --git a/src/ui/src/js/services/token_service.js b/src/ui/src/js/services/token_service.js
index 3df6356ac..b4b75e3d2 100644
--- a/src/ui/src/js/services/token_service.js
+++ b/src/ui/src/js/services/token_service.js
@@ -63,7 +63,7 @@ export default function tokenService($http, localStorageService) {
(response) => {
service.clearRefresh();
service.clearToken();
- }
+ },
);
},
});
diff --git a/src/ui/src/js/services/user_service.js b/src/ui/src/js/services/user_service.js
index e71f4e9bf..9253e4213 100644
--- a/src/ui/src/js/services/user_service.js
+++ b/src/ui/src/js/services/user_service.js
@@ -38,7 +38,7 @@ export default function userService($http) {
userId,
_.map(roles, (value) => {
return {operation: 'add', path: '/roles', value: value};
- })
+ }),
);
},
removeRoles: (userId, roles) => {
@@ -46,7 +46,7 @@ export default function userService($http) {
userId,
_.map(roles, (value) => {
return {operation: 'remove', path: '/roles', value: value};
- })
+ }),
);
},
setRoles: (userId, roles) => {
diff --git a/src/ui/src/js/services/utility_service.js b/src/ui/src/js/services/utility_service.js
index f561ff09a..ab60debf5 100644
--- a/src/ui/src/js/services/utility_service.js
+++ b/src/ui/src/js/services/utility_service.js
@@ -45,7 +45,7 @@ export function mapToArray(map) {
accumulator.push(key);
}
},
- []
+ [],
);
}