Merge pull request #751 from dod-ccpo/view-app-envs
View Application Environments Table
This commit is contained in:
commit
bf4eb23557
@ -64,11 +64,16 @@ def edit_application(portfolio_id, application_id):
|
|||||||
application = Applications.get(application_id)
|
application = Applications.get(application_id)
|
||||||
form = ApplicationForm(name=application.name, description=application.description)
|
form = ApplicationForm(name=application.name, description=application.description)
|
||||||
|
|
||||||
|
environments_obj = {}
|
||||||
|
for env in application.environments:
|
||||||
|
environments_obj[env.name] = [user.full_name for user in env.users]
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"portfolios/applications/edit.html",
|
"portfolios/applications/edit.html",
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
application=application,
|
application=application,
|
||||||
form=form,
|
form=form,
|
||||||
|
environments_obj=environments_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
34
js/components/tables/application_environments.js
Normal file
34
js/components/tables/application_environments.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { set } from 'vue/dist/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'environments-table',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
environments: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
environmentsState: this.environments,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
Object.keys(this.environments).forEach(environment => {
|
||||||
|
set(this.environmentsState[environment], 'isVisible', false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggle: function(e, environmentName) {
|
||||||
|
this.environmentsState = Object.assign(this.environmentsState, {
|
||||||
|
[environmentName]: Object.assign(
|
||||||
|
this.environmentsState[environmentName],
|
||||||
|
{
|
||||||
|
isVisible: !this.environmentsState[environmentName].isVisible,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -25,6 +25,7 @@ import Modal from './mixins/modal'
|
|||||||
import selector from './components/selector'
|
import selector from './components/selector'
|
||||||
import BudgetChart from './components/charts/budget_chart'
|
import BudgetChart from './components/charts/budget_chart'
|
||||||
import SpendTable from './components/tables/spend_table'
|
import SpendTable from './components/tables/spend_table'
|
||||||
|
import EnvironmentsTable from './components/tables/application_environments'
|
||||||
import TaskOrderList from './components/tables/task_order_list.js'
|
import TaskOrderList from './components/tables/task_order_list.js'
|
||||||
import MembersList from './components/members_list'
|
import MembersList from './components/members_list'
|
||||||
import LocalDatetime from './components/local_datetime'
|
import LocalDatetime from './components/local_datetime'
|
||||||
@ -56,6 +57,7 @@ const app = new Vue({
|
|||||||
selector,
|
selector,
|
||||||
BudgetChart,
|
BudgetChart,
|
||||||
SpendTable,
|
SpendTable,
|
||||||
|
EnvironmentsTable,
|
||||||
TaskOrderList,
|
TaskOrderList,
|
||||||
MembersList,
|
MembersList,
|
||||||
LocalDatetime,
|
LocalDatetime,
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
@import 'elements/buttons';
|
@import 'elements/buttons';
|
||||||
@import 'elements/panels';
|
@import 'elements/panels';
|
||||||
@import 'elements/block_lists';
|
@import 'elements/block_lists';
|
||||||
@import 'elements/accordians';
|
@import 'elements/accordions';
|
||||||
@import 'elements/tables';
|
@import 'elements/tables';
|
||||||
@import 'elements/sidenav';
|
@import 'elements/sidenav';
|
||||||
@import 'elements/action_group';
|
@import 'elements/action_group';
|
||||||
@ -23,6 +23,7 @@
|
|||||||
@import 'elements/graphs';
|
@import 'elements/graphs';
|
||||||
@import 'elements/menu';
|
@import 'elements/menu';
|
||||||
|
|
||||||
|
@import 'components/accordion_table';
|
||||||
@import 'components/topbar';
|
@import 'components/topbar';
|
||||||
@import 'components/top_message';
|
@import 'components/top_message';
|
||||||
@import 'components/global_layout';
|
@import 'components/global_layout';
|
||||||
|
66
styles/components/_accordion_table.scss
Normal file
66
styles/components/_accordion_table.scss
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
.accordion-table {
|
||||||
|
|
||||||
|
table {
|
||||||
|
thead th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-bottom: 1px solid $color-gray-lightest;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-table__items {
|
||||||
|
.accordion-table__item__toggler {
|
||||||
|
@include icon-link-color($color-blue, $color-gray-lightest);
|
||||||
|
float: right;
|
||||||
|
margin-left: -$gap;
|
||||||
|
color: $color-blue;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@include icon-size(12);
|
||||||
|
margin-right: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5 * $gap;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 10px solid transparent;
|
||||||
|
border-right: 10px solid transparent;
|
||||||
|
|
||||||
|
border-bottom: 10px solid $color-blue-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td, tr {
|
||||||
|
border-bottom: 1px dashed $color-gray-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[scope=rowgroup] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-table__item__expanded {
|
||||||
|
margin-left: 2 * $gap;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
.icon-link {
|
||||||
|
font-weight: $font-normal;
|
||||||
|
font-size: $base-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-bottom: 1px dashed $color-white;
|
||||||
|
background-color: $color-gray-lightest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -171,26 +171,6 @@
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-list-header {
|
|
||||||
margin: 2 * $gap 5 * $gap;
|
|
||||||
padding: inherit;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
float: left;
|
|
||||||
padding-bottom: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-link {
|
|
||||||
float: right;
|
|
||||||
margin-top: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subheading {
|
.subheading {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
@ -326,15 +306,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.members-table-footer {
|
|
||||||
float: right;
|
|
||||||
padding: 3 * $gap 0;
|
|
||||||
|
|
||||||
.action-group.save {
|
|
||||||
padding-right: 3 * $gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.modal-link.icon-link {
|
a.modal-link.icon-link {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.accordian {
|
.accordion {
|
||||||
@include block-list;
|
@include block-list;
|
||||||
|
|
||||||
box-shadow: 0 4px 10px 0 rgba(193,193,193,0.5);
|
box-shadow: 0 4px 10px 0 rgba(193,193,193,0.5);
|
||||||
@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__header {
|
.accordion__header {
|
||||||
@include block-list-header;
|
@include block-list-header;
|
||||||
border-top: 3px solid $color-blue;
|
border-top: 3px solid $color-blue;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__title {
|
.accordion__title {
|
||||||
@include block-list__title;
|
@include block-list__title;
|
||||||
color: $color-blue;
|
color: $color-blue;
|
||||||
@include h3;
|
@include h3;
|
||||||
@ -45,14 +45,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__description {
|
.accordion__description {
|
||||||
@include block-list__description;
|
@include block-list__description;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: $small-font-size;
|
font-size: $small-font-size;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__actions {
|
.accordion__actions {
|
||||||
margin-top: $gap;
|
margin-top: $gap;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -76,14 +76,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__item {
|
.accordion__item {
|
||||||
@include block-list-item;
|
@include block-list-item;
|
||||||
|
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
background-color: $color-blue-light;
|
background-color: $color-blue-light;
|
||||||
border-bottom: 1px solid rgba($color-gray-light, 0.5);
|
border-bottom: 1px solid rgba($color-gray-light, 0.5);
|
||||||
|
|
||||||
&.accordian__item--selectable {
|
&.accordion__item--selectable {
|
||||||
> div {
|
> div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
@ -117,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordian__footer {
|
.accordion__footer {
|
||||||
@include block-list__footer;
|
@include block-list__footer;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
@ -107,6 +107,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel__footer {
|
||||||
|
padding: 3 * $gap;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px dashed $color-gray-light;
|
border-bottom: 1px dashed $color-gray-light;
|
||||||
|
@ -102,6 +102,25 @@
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@include panel-margin;
|
@include panel-margin;
|
||||||
|
|
||||||
|
.responsive-table-wrapper__header {
|
||||||
|
@include panel-base;
|
||||||
|
@include panel-theme-default;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: $gap * 2;
|
||||||
|
|
||||||
|
.responsive-table-wrapper__title {
|
||||||
|
@include h4;
|
||||||
|
font-size: $lead-font-size;
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header.accordian__header {
|
.list-header {
|
||||||
padding: 1.6rem;
|
margin: 2 * $gap 5 * $gap;
|
||||||
|
padding: inherit;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-link {
|
||||||
|
.icon--info {
|
||||||
|
bottom: -1px;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
|
||||||
|
thead {
|
||||||
|
td {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
border-bottom: 1px solid $color-gray-lightest;
|
||||||
|
border-top: 0;
|
||||||
|
padding: 3 * $gap 2 * $gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,48 +201,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spend-table__month-select {
|
||||||
.spend-table {
|
|
||||||
box-shadow: 0 6px 18px 0 rgba(144,164,183,0.3);
|
|
||||||
|
|
||||||
.spend-table__header {
|
|
||||||
@include panel-base;
|
|
||||||
@include panel-theme-default;
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: $gap * 2;
|
|
||||||
|
|
||||||
.spend-table__title {
|
|
||||||
@include h4;
|
|
||||||
font-size: $lead-font-size;
|
|
||||||
flex: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spend-table__month-select {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
thead th {
|
.spend-table__portfolio {
|
||||||
text-transform: uppercase;
|
th, td {
|
||||||
|
font-weight: bold;
|
||||||
border-bottom: 1px solid $color-gray-lightest;
|
border-bottom: 1px solid $color-gray-lightest;
|
||||||
border-top: none;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.previous-month {
|
&.previous-month {
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
}
|
}
|
||||||
@ -287,59 +259,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spend-table__portfolio {
|
|
||||||
th, td {
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid $color-gray-lightest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spend-table__application {
|
|
||||||
.spend-table__application__toggler {
|
|
||||||
@include icon-link-color($color-blue, $color-gray-lightest);
|
|
||||||
margin-left: -$gap;
|
|
||||||
color: $color-blue;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
@include icon-size(12);
|
|
||||||
margin-right: $gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open-indicator {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 5 * $gap;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 10px solid transparent;
|
|
||||||
border-right: 10px solid transparent;
|
|
||||||
|
|
||||||
border-bottom: 10px solid $color-blue-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
th[scope=rowgroup] {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spend-table__application__env {
|
|
||||||
margin-left: 2 * $gap;
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
.icon-link {
|
|
||||||
font-weight: $font-normal;
|
|
||||||
font-size: $base-font-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
border-bottom: 1px dashed $color-white;
|
|
||||||
background-color: $color-blue-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
{% from "components/alert.html" import Alert %}
|
{% from "components/alert.html" import Alert %}
|
||||||
|
|
||||||
<section class="member-list" id="portfolio-members">
|
<section class="member-list" id="portfolio-members">
|
||||||
<div class='responsive-table-wrapper panel'>
|
<div class='responsive-table-wrapper panel accordion-table'>
|
||||||
{% if g.matchesPath("portfolio-members") %}
|
{% if g.matchesPath("portfolio-members") %}
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -14,18 +14,22 @@
|
|||||||
<form method='POST' id="member-perms" action='{{ url_for("portfolios.edit_portfolio_members", portfolio_id=portfolio.id) }}' autocomplete="off" enctype="multipart/form-data">
|
<form method='POST' id="member-perms" action='{{ url_for("portfolios.edit_portfolio_members", portfolio_id=portfolio.id) }}' autocomplete="off" enctype="multipart/form-data">
|
||||||
{{ member_perms_form.csrf_token }}
|
{{ member_perms_form.csrf_token }}
|
||||||
|
|
||||||
<div class='member-list-header'>
|
<div class='application-list-item'>
|
||||||
<div class='left'>
|
<header>
|
||||||
|
<div class='responsive-table-wrapper__header'>
|
||||||
|
<div class='responsive-table-wrapper__title'>
|
||||||
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
||||||
<div class='subheading'>
|
<div class='subheading'>
|
||||||
{{ "portfolios.admin.portfolio_members_subheading" | translate }}
|
{{ "portfolios.admin.portfolio_members_subheading" | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class='icon-link'>
|
<a class='icon-link'>
|
||||||
{{ Icon('info') }}
|
{{ Icon('info') }}
|
||||||
{{ "portfolios.admin.settings_info" | translate }}
|
{{ "portfolios.admin.settings_info" | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
{% if not portfolio.members %}
|
{% if not portfolio.members %}
|
||||||
<p>{{ "portfolios.admin.no_members" | translate }}</p>
|
<p>{{ "portfolios.admin.no_members" | translate }}</p>
|
||||||
@ -52,15 +56,20 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
<div class="members-table-footer">
|
</div>
|
||||||
|
<div class="panel__footer">
|
||||||
<div class="action-group save">
|
<div class="action-group save">
|
||||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||||
{{ SaveButton(text=('common.save' | translate), element="input", form="member-perms") }}
|
{{ SaveButton(text=('common.save' | translate), element="input", form="member-perms") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||||
|
{% include "fragments/admin/add_new_portfolio_member.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
|
|
||||||
@ -78,6 +87,7 @@
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
<div class="panel__footer">
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, user_id=member.user_id) }}">
|
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, user_id=member.user_id) }}">
|
||||||
{{ member_perms_form.csrf_token }}
|
{{ member_perms_form.csrf_token }}
|
||||||
@ -87,17 +97,11 @@
|
|||||||
</form>
|
</form>
|
||||||
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">{{ "common.cancel" | translate }}</a>
|
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">{{ "common.cancel" | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="members-table-footer">
|
|
||||||
<div class="action-group">
|
|
||||||
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
|
||||||
{% include "fragments/admin/add_new_portfolio_member.html" %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
50
templates/fragments/applications/environments.html
Normal file
50
templates/fragments/applications/environments.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
|
<div class="application-list-item">
|
||||||
|
<header>
|
||||||
|
<div class="responsive-table-wrapper__header">
|
||||||
|
<div class='responsive-table-wrapper__title'>
|
||||||
|
<div class='h3'>{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
||||||
|
</div>
|
||||||
|
<a class='icon-link'>
|
||||||
|
{{ Icon('info') }}
|
||||||
|
{{ "portfolios.admin.settings_info" | translate }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<environments-table
|
||||||
|
v-cloak
|
||||||
|
v-bind:environments='{{ environments_obj }}'
|
||||||
|
inline-template>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th scope='col'>{{ "portfolios.applications.environments.name" | translate }}</th>
|
||||||
|
<th scope='col' class='table-cell--align-right'>{{ "portfolios.applications.environments.members" | translate }}</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody v-for='(members_list, name) in environments' class='accordion-table__items'>
|
||||||
|
<tr>
|
||||||
|
<th scope='rowgroup' v-on:click="toggle($event, name)" v-html='name'></th>
|
||||||
|
|
||||||
|
<template v-if="environmentsState[name].isVisible">
|
||||||
|
<td v-on:click="toggle($event, name)" class='icon-link icon-link--large accordion-table__item__toggler'>Hide Members (<span v-html='members_list.length'></span>){{ Icon('caret_up') }}</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td v-on:click="toggle($event, name)" class='icon-link icon-link--large accordion-table__item__toggler'>Show Members (<span v-html='members_list.length'></span>){{ Icon('caret_down') }}</td>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr scope='rowgroup' v-for='member in members_list' v-show='environmentsState[name].isVisible' class='accordion-table__item__expanded'>
|
||||||
|
<td>
|
||||||
|
<div>
|
||||||
|
<span v-html='member'></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class='table-cell--expand'></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</environments-table>
|
||||||
|
</div>
|
@ -1,6 +1,7 @@
|
|||||||
{% extends "portfolios/applications/base.html" %}
|
{% extends "portfolios/applications/base.html" %}
|
||||||
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
{% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %}
|
{% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %}
|
||||||
|
|
||||||
@ -12,33 +13,28 @@
|
|||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel__content">
|
<div class="panel__content">
|
||||||
|
|
||||||
{% include "fragments/edit_application_form.html" %}
|
{% include "fragments/applications/edit_application_form.html" %}
|
||||||
|
|
||||||
<div class="application-list-item">
|
|
||||||
<header>
|
|
||||||
<h2 class="block-list__title">{{ 'portfolios.applications.environments_heading' | translate }}</h2>
|
|
||||||
<p>
|
|
||||||
{{ 'portfolios.applications.environments_description' | translate }}
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for environment in application.environments %}
|
|
||||||
<li class="application-edit__env-list-item">
|
|
||||||
<div class="usa-input input--disabled">
|
|
||||||
<label>Environment Name</label>
|
|
||||||
<input type="text" disabled value="{{ environment.name }}" readonly />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="panel__footer">
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="accordion-table responsive-table-wrapper panel">
|
||||||
|
{% include "fragments/applications/environments.html" %}
|
||||||
|
<div class="panel__footer">
|
||||||
|
<div class="action-group">
|
||||||
|
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
||||||
|
<a class='icon-link'>
|
||||||
|
{{ "portfolios.applications.add_environment" | translate }}
|
||||||
|
{{ Icon('plus-circle-solid') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -34,13 +34,13 @@
|
|||||||
|
|
||||||
<div class='application-list'>
|
<div class='application-list'>
|
||||||
{% for application in portfolio.applications|sort(attribute='name') %}
|
{% for application in portfolio.applications|sort(attribute='name') %}
|
||||||
<div is='toggler' v-cloak class='accordian application-list-item'>
|
<div is='toggler' v-cloak class='accordion application-list-item'>
|
||||||
<template slot-scope='props'>
|
<template slot-scope='props'>
|
||||||
<header class='accordian__header row'>
|
<header class='accordion__header row'>
|
||||||
<div class='col col-grow'>
|
<div class='col col-grow'>
|
||||||
<h3 class='icon-link accordian__title' v-on:click="props.toggle">{{ application.name }}</h3>
|
<h3 class='icon-link accordion__title' v-on:click="props.toggle">{{ application.name }}</h3>
|
||||||
<span class='accordian__description'>{{ application.description }}</span>
|
<span class='accordion__description'>{{ application.description }}</span>
|
||||||
<div class='accordian__actions'>
|
<div class='accordion__actions'>
|
||||||
{% if user_can(permissions.EDIT_APPLICATION) %}
|
{% if user_can(permissions.EDIT_APPLICATION) %}
|
||||||
<a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'>
|
<a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'>
|
||||||
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
|
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
|
||||||
@ -68,7 +68,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<ul v-if="props.isVisible">
|
<ul v-if="props.isVisible">
|
||||||
{% for environment in application.environments %}
|
{% for environment in application.environments %}
|
||||||
<li class='accordian__item application-list-item__environment'>
|
<li class='accordion__item application-list-item__environment'>
|
||||||
<div class='application-list-item__environment__name'>
|
<div class='application-list-item__environment__name'>
|
||||||
<span>{{ environment.name }}</span>
|
<span>{{ environment.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
{% include "fragments/edit_application_form.html" %}
|
{% include "fragments/applications/edit_application_form.html" %}
|
||||||
|
|
||||||
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
|
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
|
||||||
<div v-cloak v-for="title in errors" :key="title">
|
<div v-cloak v-for="title in errors" :key="title">
|
||||||
|
@ -346,9 +346,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</budget-chart>
|
</budget-chart>
|
||||||
|
|
||||||
<div class='spend-table responsive-table-wrapper'>
|
<div class='accordion-table responsive-table-wrapper'>
|
||||||
<div class='spend-table__header'>
|
<div class='responsive-table-wrapper__header'>
|
||||||
<h2 class='spend-table__title'>Total spent per month</h2>
|
<h2 class='responsive-table-wrapper__title'>Total spent per month</h2>
|
||||||
|
|
||||||
<select name='month' id='month' onchange='location = this.value' class='spend-table__month-select'>
|
<select name='month' id='month' onchange='location = this.value' class='spend-table__month-select'>
|
||||||
{% for m in cumulative_budget["months"] %}
|
{% for m in cumulative_budget["months"] %}
|
||||||
@ -402,10 +402,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
<tbody v-for='(application, name) in applicationsState' class='spend-table__application'>
|
<tbody v-for='(application, name) in applicationsState' class='accordion-table__items'>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope='rowgroup'>
|
<th scope='rowgroup'>
|
||||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__application__toggler'>
|
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large accordion-table__item__toggler'>
|
||||||
<template v-if='application.isVisible'>{{ Icon('caret_down') }}<div class='open-indicator'></div></template>
|
<template v-if='application.isVisible'>{{ Icon('caret_down') }}<div class='open-indicator'></div></template>
|
||||||
<template v-else>{{ Icon('caret_right') }}</template>
|
<template v-else>{{ Icon('caret_right') }}</template>
|
||||||
<span v-html='name'></span>
|
<span v-html='name'></span>
|
||||||
@ -433,9 +433,9 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='spend-table__application__env'>
|
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='accordion-table__item__expanded'>
|
||||||
<th scope='rowgroup'>
|
<th scope='rowgroup'>
|
||||||
<div class='icon-link spend-table__application__env'>
|
<div class='icon-link accordion-table__item__expanded'>
|
||||||
<span v-html='envName'></span>
|
<span v-html='envName'></span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
@ -13,6 +13,8 @@ from atst.domain.applications import Applications
|
|||||||
from atst.domain.portfolios import Portfolios
|
from atst.domain.portfolios import Portfolios
|
||||||
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||||
|
|
||||||
|
from tests.utils import captured_templates
|
||||||
|
|
||||||
|
|
||||||
def test_user_with_permission_has_budget_report_link(client, user_session):
|
def test_user_with_permission_has_budget_report_link(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
@ -105,6 +107,41 @@ def test_view_edit_application(client, user_session):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_application_environments_obj(app, client, user_session):
|
||||||
|
portfolio = PortfolioFactory.create()
|
||||||
|
application = Applications.create(
|
||||||
|
portfolio,
|
||||||
|
"Snazzy Application",
|
||||||
|
"A new application for me and my friends",
|
||||||
|
{"env1", "env2"},
|
||||||
|
)
|
||||||
|
user1 = UserFactory.create()
|
||||||
|
user2 = UserFactory.create()
|
||||||
|
env1 = application.environments[0]
|
||||||
|
env2 = application.environments[1]
|
||||||
|
EnvironmentRoleFactory.create(environment=env1, user=user1)
|
||||||
|
EnvironmentRoleFactory.create(environment=env1, user=user2)
|
||||||
|
EnvironmentRoleFactory.create(environment=env2, user=user1)
|
||||||
|
|
||||||
|
user_session(portfolio.owner)
|
||||||
|
|
||||||
|
with captured_templates(app) as templates:
|
||||||
|
response = app.test_client().get(
|
||||||
|
url_for(
|
||||||
|
"portfolios.edit_application",
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
application_id=application.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
_, context = templates[0]
|
||||||
|
assert context["environments_obj"] == {
|
||||||
|
env1.name: [user1.full_name, user2.full_name],
|
||||||
|
env2.name: [user1.full_name],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_user_with_permission_can_update_application(client, user_session):
|
def test_user_with_permission_can_update_application(client, user_session):
|
||||||
owner = UserFactory.create()
|
owner = UserFactory.create()
|
||||||
portfolio = PortfolioFactory.create(
|
portfolio = PortfolioFactory.create(
|
||||||
|
@ -580,13 +580,17 @@ portfolios:
|
|||||||
existing_application_title: '{application_name} Application Settings'
|
existing_application_title: '{application_name} Application Settings'
|
||||||
new_application_title: New Application
|
new_application_title: New Application
|
||||||
settings_heading: Application Settings
|
settings_heading: Application Settings
|
||||||
environments_heading: Environments
|
environments_heading: Application Environments
|
||||||
environments_description: Each environment created within an application is logically separated from one another for easier management and security.
|
environments_description: Each environment created within an application is logically separated from one another for easier management and security.
|
||||||
update_button_text: Save
|
update_button_text: Save
|
||||||
create_button_text: Create
|
create_button_text: Create
|
||||||
team_management:
|
team_management:
|
||||||
title: '{application_name} Team Management'
|
title: '{application_name} Team Management'
|
||||||
subheading: Team Management
|
subheading: Team Management
|
||||||
|
environments:
|
||||||
|
name: Name
|
||||||
|
members: Members
|
||||||
|
add_environment: Add New Environment
|
||||||
admin:
|
admin:
|
||||||
portfolio_members_title: Portfolio members
|
portfolio_members_title: Portfolio members
|
||||||
portfolio_members_subheading: These members have different levels of access to the portfolio.
|
portfolio_members_subheading: These members have different levels of access to the portfolio.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user