diff --git a/atst/domain/csp/cloud.py b/atst/domain/csp/cloud.py index 9f764da0..b04ed91f 100644 --- a/atst/domain/csp/cloud.py +++ b/atst/domain/csp/cloud.py @@ -341,6 +341,12 @@ class CloudProviderInterface: """ raise NotImplementedError() + def create_subscription(self, environment): + """Returns True if a new subscription has been created or raises an + exception if an error occurs while creating a subscription. + """ + raise NotImplementedError() + class MockCloudProvider(CloudProviderInterface): @@ -508,6 +514,11 @@ class MockCloudProvider(CloudProviderInterface): return self._maybe(12) + def create_subscription(self, environment): + self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException) + + return True + def get_calculator_url(self): return "https://www.rackspace.com/en-us/calculator" diff --git a/atst/routes/applications/index.py b/atst/routes/applications/index.py index 92b65398..8b249faa 100644 --- a/atst/routes/applications/index.py +++ b/atst/routes/applications/index.py @@ -26,7 +26,7 @@ def has_portfolio_applications(_user, portfolio=None, **_kwargs): def portfolio_applications(portfolio_id): user_env_roles = EnvironmentRoles.for_user(g.current_user.id, portfolio_id) environment_access = { - env_role.environment_id: env_role.role for env_role in user_env_roles + env_role.environment_id: env_role.role.value for env_role in user_env_roles } return render_template( diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index da04bc84..b4e75fc1 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -1,9 +1,10 @@ from flask import ( + current_app as app, + g, redirect, render_template, request as http_request, url_for, - g, ) from .blueprint import applications_bp @@ -522,3 +523,31 @@ def resend_invite(application_id, application_role_id): _anchor="application-members", ) ) + + +@applications_bp.route( + "/environments//add_subscription", methods=["POST"] +) +@user_can(Permissions.EDIT_ENVIRONMENT, message="create new environment subscription") +def create_subscription(environment_id): + environment = Environments.get(environment_id) + + try: + app.csp.cloud.create_subscription(environment) + flash("environment_subscription_success", name=environment.displayname) + + except GeneralCSPException: + flash("environment_subscription_failure") + return ( + render_settings_page(application=environment.application, show_flash=True), + 400, + ) + + return redirect( + url_for( + "applications.settings", + application_id=environment.application.id, + fragment="application-environments", + _anchor="application-environments", + ) + ) diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 02a74cb2..ea85f1ef 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -83,6 +83,16 @@ MESSAGES = { "message": "flash.environment.deleted.message", "category": "success", }, + "environment_subscription_failure": { + "title": "flash.environment.subscription_failure.title", + "message": "flash.environment.subscription_failure.message", + "category": "error", + }, + "environment_subscription_success": { + "title": "flash.environment.subscription_success.title", + "message": "flash.environment.subscription_success.message", + "category": "success", + }, "form_errors": { "title": "flash.form.errors.title", "message": "flash.form.errors.message", diff --git a/styles/components/_accordion_table.scss b/styles/components/_accordion_table.scss index 9a32c009..67e6fb0a 100644 --- a/styles/components/_accordion_table.scss +++ b/styles/components/_accordion_table.scss @@ -106,7 +106,7 @@ &__expanded { font-size: $small-font-size; font-weight: $font-normal; - background-color: $color-gray-lightest; + background-color: $color-offwhite; padding: $gap; &:last-child { diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 81a15b69..d01c1427 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -222,14 +222,21 @@ span.accordion-table__item__toggler { font-weight: $font-normal; + text-decoration: underline; + font-size: $small-font-size; &.environment-list__item__members { float: unset; - font-size: $small-font-size; + margin-left: -$gap; } } } + li.environment-list__edit { + border: 1px solid $color-gray-lighter; + padding: 0 $gap * 3 $gap * 2; + } + .activity-log { border-top: 3px solid $color-blue; diff --git a/templates/applications/fragments/environments.html b/templates/applications/fragments/environments.html index 8676bc6d..fa4e5959 100644 --- a/templates/applications/fragments/environments.html +++ b/templates/applications/fragments/environments.html @@ -17,7 +17,7 @@ {% if 0 == environments_obj | length -%}

- This Application has no environments + {{ 'portfolios.applications.environments.blank_slate' | translate }}

{% else %} @@ -31,8 +31,23 @@
- {{ env['name'] }} + + {{ env['name'] }} {{ Icon('link', classes='icon--medium icon--primary') }} + + {% if user_can(permissions.EDIT_ENVIRONMENT) -%} + {{ + ToggleButton( + open_html="common.edit"|translate, + close_html="common.close"|translate, + section_name="edit" + ) + }} + {%- endif %} +
{% set members_button = "portfolios.applications.member_count" | translate({'count': env['member_count']}) %} {{ ToggleButton( @@ -42,23 +57,9 @@ classes="environment-list__item__members" ) }} - {% if user_can(permissions.EDIT_ENVIRONMENT) -%} - {% set edit_environment_button = "Edit" %} - {{ - ToggleButton( - open_html=edit_environment_button, - close_html=edit_environment_button, - section_name="edit" - ) - }} - {%- endif %}
{% if env['pending'] -%} {{ Label(type="changes_pending", classes='label--below')}} - {% else %} - - {{ "portfolios.applications.csp_link" | translate }} {{ Icon('link', classes="icon--tiny") }} - {%- endif %}
@@ -66,7 +67,7 @@ {% call ToggleSection(section_name="members") %}