workspace -> portfolio everywhere

This commit is contained in:
dandds
2019-01-11 09:58:00 -05:00
parent 3fc323d785
commit d3d36822df
122 changed files with 2156 additions and 2129 deletions

View File

@@ -1,9 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/pagination.html" import Pagination %}
{% block workspace_content %}
<div v-cloak>
{% include "fragments/audit_events_log.html" %}
{{ Pagination(audit_events, 'workspaces.workspace_activity', workspace_id=workspace_id) }}
</div>
{% endblock %}

View File

@@ -1,36 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/text_input.html" import TextInput %}
{% block workspace_content %}
<form method="POST" action="{{ url_for('workspaces.edit_application', workspace_id=workspace.id, application_id=application.id) }}">
{% include "fragments/edit_application_form.html" %}
<div class="block-list application-list-item">
<header class="block-list__header block-list__header--grow">
<h2 class="block-list__title">Application Environments</h2>
<p>
Each environment created within an application is an enclave of cloud resources that is logically separated from each other for increased security.
</p>
</header>
<ul>
{% for environment in application.environments %}
<li class="block-list__item application-edit__env-list-item">
<div class="usa-input">
<label>Environment Name</label>
<input type="text" value="{{ environment.name }}" readonly />
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="action-group">
<button class="usa-button usa-button-primary" tabindex="0" type="submit">Update Application</button>
</div>
</form>
{% endblock %}

View File

@@ -1,55 +0,0 @@
{% from "components/icon.html" import Icon %}
{% from "components/empty_state.html" import EmptyState %}
{% extends "workspaces/base.html" %}
{% block workspace_content %}
{% if not workspace.applications %}
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
{{ EmptyState(
'This portfolio doesnt have any applications yet.',
action_label='Add a New Application' if can_create_applications else None,
action_href=url_for('workspaces.new_application', workspace_id=workspace.id) if can_create_applications else None,
icon='cloud',
sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.'
) }}
{% else %}
{% for application in workspace.applications %}
<div v-cloak class='block-list application-list-item'>
<header class='block-list__header'>
<h2 class='block-list__title'>{{ application.name }} ({{ application.environments|length }} environments)</h2>
{% if user_can(permissions.RENAME_APPLICATION_IN_WORKSPACE) %}
<a class='icon-link' href='{{ url_for("workspaces.edit_application", workspace_id=workspace.id, application_id=application.id) }}'>
{{ Icon('edit') }}
<span>edit</span>
</a>
{% endif %}
</header>
<ul>
{% for environment in application.environments %}
<li class='block-list__item application-list-item__environment'>
<a href='{{ url_for("workspaces.access_environment", workspace_id=workspace.id, environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__link'>
{{ Icon('link') }}
<span>{{ environment.name }}</span>
</a>
<div class='application-list-item__environment__members'>
<div class='label'>{{ environment.num_users }}</div>
<span>members</span>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -1,74 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/alert.html" import Alert %}
{% from "components/icon.html" import Icon %}
{% from "components/modal.html" import Modal %}
{% from "components/text_input.html" import TextInput %}
{% block workspace_content %}
{% set modalName = "newApplicationConfirmation" %}
{% include "fragments/flash.html" %}
<new-application inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
<form method="POST" action="{{ url_for('workspaces.create_application', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
{% call Modal(name=modalName, dismissable=False) %}
<h1>Create application !{ name }</h1>
<p>
When you click <em>Create Application</em>, the environments
<span v-for="(environment, index) in environments">
<strong>!{environment.name}</strong><template v-if="index < (environments.length - 1)">, </template>
</span>
will be created as individual cloud resource groups under <strong>!{ name }</strong> application.
</p>
<div class='action-group'>
<button autofocus type='submit' class='action-group__action usa-button' tabindex='0'>Create Application</button>
<button type='button' v-on:click="handleCancelSubmit" class='icon-link action-group__action' tabindex='0'>Cancel</button>
</div>
{% endcall %}
{% include "fragments/edit_application_form.html" %}
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
<div v-cloak v-for="title in errors" :key="title">
{{ Alert(message=None, level="error", vue_template=True) }}
</div>
</div>
<div class="block-list application-list-item">
<header class="block-list__header block-list__header--grow">
<h2 class="block-list__title">Application Environments</h2>
<p>
Each environment created within an application is an enclave of cloud resources that is logically separated from each other for increased security.
</p>
</header>
<ul>
<li v-for="(environment, i) in environments" class="block-list__item application-edit__env-list-item">
<div class="usa-input">
<label :for="'environment_names-' + i">Environment Name</label>
<input type="text" :id="'environment_names-' + i" v-model="environment.name" placeholder="e.g. Development, Staging, Production"/>
<input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
</div>
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class='application-edit__env-list-item__remover'>
{{ Icon('trash') }}
<span>Remove</span>
</button>
</li>
</ul>
<div class="block-list__footer">
<button v-on:click="addEnvironment" class="icon-link" tabindex="0" type="button">Add another environment</button>
</div>
</div>
<div class="action-group">
<button class="usa-button usa-button-primary" tabindex="0" type="submit">Create Application</button>
</div>
</form>
</new-application>
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class='workspace-panel-container'>
<div class='col'>
{% include 'navigation/workspace_navigation.html' %}
</div>
<div class='col col--grow'>
{% block workspace_content %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,36 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/icon.html" import Icon %}
{% from "components/text_input.html" import TextInput %}
{% block workspace_content %}
{% include "fragments/flash.html" %}
<form method="POST" action="{{ url_for('workspaces.edit_workspace', workspace_id=workspace.id) }}" autocomplete="false">
{{ form.csrf_token }}
<div class="panel">
<div class="panel__heading">
<h1>Portfolio Settings</h1>
</div>
<div class="panel__content">
{{ TextInput(form.name, validation="workspaceName") }}
</div>
</div>
<div class='action-group'>
<button type="submit" class="usa-button usa-button-big usa-button-primary" tabindex="0">Save</button>
<a href='{{ url_for("workspaces.workspace_applications", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
{{ Icon('x') }}
<span>Cancel</span>
</a>
</div>
</form>
{% endblock %}

View File

@@ -1,31 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div v-cloak class='col'>
<table>
<thead>
<tr>
<th>Portfolio Name</th>
<th>Task Order</th>
<th>Users</th>
</tr>
</thead>
<tbody>
{% for workspace in workspaces %}
<tr>
<td>
<a class='icon-link icon-link--large' href="/workspaces/{{ workspace.id }}/applications">{{ workspace.name }}</a><br>
</td>
<td>
#{{ workspace.legacy_task_order.number }}
</td>
<td>
<span class="label">{{ workspace.user_count }}</span><span class='h6'>Users</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -1,190 +0,0 @@
{% extends "base.html" %}
{% from "components/icon.html" import Icon %}
{% from "components/modal.html" import Modal %}
{% from "components/selector.html" import Selector %}
{% from "components/options_input.html" import OptionsInput %}
{% from "components/confirmation_button.html" import ConfirmationButton %}
{% block content %}
{% include "fragments/flash.html" %}
<form method="POST" action="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=member.user_id) }}" autocomplete="false">
{{ form.csrf_token }}
<div class='panel member-card'>
<div class='member-card__header'>
<h1 class='member-card__heading'>{{ member.user.full_name }}</h1>
<div class="usa-input member-card__input">
{{ Selector(form.workspace_role) }}
</div>
</div>
<div class='member-card__details'>
<dl>
<div>
<dt>DOD ID:</dt>
<dd>{{ member.user.dod_id }}</dd>
</div>
<div>
<dt>Email:</dt>
<dd>{{ member.user.email }}</dd>
</div>
</dl>
{% if editable %}
<a href='{{ url_for("users.user") }}' class='icon-link'>edit account details</a>
{% endif %}
<div>
{% if member.latest_invitation.is_revokable %}
{{ ConfirmationButton(
"Revoke Invitation",
url_for("workspaces.revoke_invitation", workspace_id=workspace.id, token=member.latest_invitation.token),
) }}
{% endif %}
{% if member.can_resend_invitation %}
{{ ConfirmationButton (
"Resend Invitation",
url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=member.latest_invitation.token),
confirm_msg="Are you sure? This will send an email to invite the user to join this portfolio."
)}}
{% endif %}
{% if can_revoke_access %}
{{ ConfirmationButton (
"Remove Portfolio Access",
url_for("workspaces.revoke_access", workspace_id=workspace.id, member_id=member.id),
confirm_msg="Are you sure? This will remove this user from the portfolio.",
)}}
{% endif %}
</div>
</div>
</div>
<div class="panel">
<div class="panel__heading panel__heading--tight">
<h2 class="h3">Manage Access <div class="subtitle">Grant access to an environment</div></h2>
</div>
</div>
<div class='search-bar'>
<div class='usa-input search-input'>
<label for='application-search'>Search by application name</label>
<input type='search' id='application-search' name='application-search' placeholder="Search by application name"/>
<button type="submit">
<span class="hide">Search</span>
</button>
</div>
</div>
{% for application in applications %}
{% set revoke_modal_name = (application.id|string) + 'RevokeModal' %}
<edit-application-roles inline-template name="{{ application.name }}" id="{{ application.id }}">
<div is='toggler' default-visible class='block-list application-list-item'>
<template slot-scope='props'>
<header class='block-list__header'>
<button v-on:click='props.toggle' class='icon-link icon-link--large icon-link--default spend-table__application__toggler'>
<template v-if='props.isVisible'>{{ Icon('caret_down') }}</template>
<template v-else>{{ Icon('caret_right') }}</template>
<h3 class="block-list__title">{{ application.name }}</h3>
</button>
<span><a v-on:click="openModal('{{ revoke_modal_name }}')" class="icon-link icon-link--danger">revoke all access</a></span>
</header>
{% call Modal(name=revoke_modal_name, dismissable=False) %}
<div>
<h1>Revoke Access</h1>
<p>
Confirming will revoke access for {{ member.user.full_name }} to any environments associated with {{ application.name }}.
</p>
<div class='action-group'>
<a v-on:click="doRevoke(); closeModal('{{ revoke_modal_name }}')" class='action-group__action usa-button'>Confirm</a>
<a class='action-group__action icon-link icon-link--danger' v-on:click="closeModal('{{ revoke_modal_name }}'); cancel();">Cancel</a>
</div>
</div>
{% endcall %}
<ul v-show='props.isVisible'>
{% for env in application.environments %}
{% set role = EnvironmentRoles.get(member.user_id, env.id).role %}
{% set env_modal_name = (env.id|string) + 'RolesModal' %}
<li class='block-list__item'>
<edit-environment-role inline-template initial-data='{{ role or "" }}' v-bind:choices='{{ choices | tojson }}' v-bind:application-id="'{{ application.id }}'">
<div class='application-list-item__environment'>
<span class='application-list-item__environment__link'>
{{ env.name }}
</span>
<div class='application-list-item__environment__actions'>
<span v-bind:class="label_class" v-html:on=displayName></span>
<button v-on:click="openModal('{{env_modal_name}}')" type="button" class="icon-link">set role</button>
{% call Modal(name=env_modal_name, dismissable=False) %}
<div class='block-list'>
<div class='block-list__header'>
<div>
{% if env_role_modal_description %}
<h1>{{ env_role_modal_description.header }}</h1>
<p>{{ env_role_modal_description.body | safe }}</p>
{% endif %}
</div>
</div>
<ul>
{% for choice in choices %}
<li class='block-list__item block-list__item--selectable'>
<input
name='radio_input_{{ env.id }}'
v-on:change.prevent='change'
type='radio'
id="env_{{ env.id }}_{{ choice[0] }}"
value='{{ choice[0] }}'
:checked="new_role === '{{ choice[0]}}'"
/>
<label for="env_{{ env.id }}_{{ choice[0] }}">
{% if choice[1].description %}
<dl>
<dt>{{ choice[1].name }}</dt>
<dd>{{ choice[1].description }}</dd>
</dl>
{% else %}
{{ choice[1].name }}
{% endif %}
</label>
</li>
{% endfor %}
</ul>
<input type='hidden' name='env_{{ env.id }}' v-bind:value='newRole'/>
<div class='block-list__footer'>
<div class='action-group'>
<a v-on:click="closeModal('{{env_modal_name}}')" class='action-group__action usa-button'>Select Access Role</a>
<a class='action-group__action icon-link icon-link--danger' v-on:click="closeModal('{{env_modal_name}}'); cancel();" value="{{ value if value == role else role }}" >Cancel</a>
</div>
</div>
</div>
{% endcall %}
</div>
</div>
</edit-environment-role>
</li>
{% endfor %}
</ul>
</template>
</div>
</edit-application-roles>
{% endfor %}
<div class='action-group'>
<button class='action-group__action usa-button usa-button-big'>
{% if is_new_member %}Create{% else %}Save{% endif %}
</button>
<a href='{{ url_for("workspaces.workspace_members", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
{{ Icon('x') }}
<span>Cancel</span>
</a>
</div>
</form>
{% endblock %}

View File

@@ -1,113 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/empty_state.html" import EmptyState %}
{% from "components/icon.html" import Icon %}
{% block workspace_content %}
{% if not workspace.members %}
{% set user_can_invite = user_can(permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE) %}
{{ EmptyState(
'There are currently no members in this Portfolio.',
action_label='Invite a new Member' if user_can_invite else None,
action_href='/members/new' if user_can_invite else None,
sub_message=None if user_can_invite else 'Please contact your JEDI Cloud portfolio administrator to invite new members.',
icon='avatar'
) }}
{% else %}
{% include "fragments/flash.html" %}
<members-list
inline-template
id="search-template"
v-bind:members='{{ members | tojson}}'
v-bind:role_choices='{{ role_choices | tojson}}'
v-bind:status_choices='{{ status_choices | tojson}}'>
<div>
<form class='search-bar' @submit.prevent>
<div class='usa-input search-input'>
<label for='members-search'>Search members by name</label>
<input v-model='searchValue' type='search' id='members-search' name='members-search' placeholder="Search by name"/>
<button type="button"></button>
</div>
<div class="search-bar__filters">
<div class='usa-input'>
<label for='filter-status'>Filter members by status</label>
<select v-model="status" id="filter-status" name="filter-status">
<option value="" selected disabled>Filter by status</option>
<option value="all">View All</option>
{% for status in status_choices %}
<option value='{{ status.name }}'>{{ status.display_name }}</option>
{% endfor %}
</select>
</div>
<div class='usa-input'>
<label for='filter-role'>Filter members by role</label>
<select v-model="role" id="filter-role" name="filter-role">
<option value="" selected disabled>Filter by role</option>
<option value="all">View All</option>
{% for role in role_choices %}
<option value='{{ role.name }}'>{{ role.display_name }}</option>
{% endfor %}
</select>
</div>
</div>
</form>
<div class='responsive-table-wrapper'>
<table v-cloak v-if='searchedList && searchedList.length'>
<thead>
<tr>
<th v-for="col in getColumns()" @click="updateSort(col.displayName)" :width="col.width" :class="col.class" scope="col">
!{ col.displayName }
<span v-if="col.displayName === sortInfo.columnName && sortInfo.isAscending">
{{ Icon("caret_down") }}
</span>
<span v-if="col.displayName === sortInfo.columnName && !sortInfo.isAscending">
{{ Icon("caret_up") }}
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for='member in searchedList'>
<td>
<a :href="member.edit_link" class="icon-link icon-link--large" v-html="member.name"></a>
</td>
<td class="table-cell--align-right" v-if='member.num_env'>
<span v-html="member.num_env"></span>
</td>
<td class='table-cell--shrink' v-else>
<span class="label label--info">No Environment Access</span>
</td>
<td v-html="member.status"></td>
<td v-html="member.role"></td>
</tr>
</tbody>
</table>
<div v-else>
{{ EmptyState(
'No members found.',
action_label=None,
action_href=None,
sub_message='Please try a different search.',
icon=None
) }}
</div>
</div>
</div>
</members-list>
{% endif %}
{% endblock %}

View File

@@ -1,44 +0,0 @@
{% extends "base.html" %}
{% from "components/icon.html" import Icon %}
{% from "components/text_input.html" import TextInput %}
{% from "components/options_input.html" import OptionsInput %}
{% from "components/selector.html" import Selector %}
{% block content %}
<form method="POST" action="{{ url_for('workspaces.create_member', workspace_id=workspace.id) }}" autocomplete="false">
{{ form.csrf_token }}
<div class="panel">
<div class="panel__heading">
<h1>New Member</h1>
<div class="subtitle"><h2>Account Details</h2></div>
</div>
<div class="panel__content">
{{ TextInput(form.first_name) }}
{{ TextInput(form.last_name) }}
{{ TextInput(form.email,placeholder='jane@mail.mil', validation='email') }}
{{ TextInput(form.dod_id,placeholder='10-digit number on the back of the CAC', validation='dodId') }}
{{ Selector(form.workspace_role) }}
</div>
</div>
<div class='action-group'>
<button class="usa-button usa-button-big usa-button-primary" tabindex="0">Add User</button>
<a href='{{ url_for("workspaces.workspace_members", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
{{ Icon('x') }}
<span>Cancel</span>
</a>
</div>
</form>
{% endblock %}

View File

@@ -1,446 +0,0 @@
{% extends "workspaces/base.html" %}
{% from "components/alert.html" import Alert %}
{% from "components/icon.html" import Icon %}
{% from "components/empty_state.html" import EmptyState %}
{% block workspace_content %}
{{ Alert("Budget Report for Portfolio " + workspace.name,
message="<p>Track your monthly and cumulative expenditures for your portfolio, applications, and environments below.</p>\
<p>Please note that the projected spend is based on the <em>average expense over the last three completed months</em> and therefore does not account for future changes that might be made in scale or configuration of your cloud services.</p>",
actions=[
{"label": "Learn More", "href": url_for('atst.helpdocs'), "icon": "info"}
] ) }}
<div v-cloak class='funding-summary-row'>
<div class='funding-summary-row__col'>
<div class='panel spend-summary'>
<div class='row'>
<h2 class='spend-summary__heading col'>Portfolio Total Spend</h2>
<dl class='spend-summary__budget'>
{% set budget = workspace_totals['budget'] %}
{% set spent = workspace_totals['spent'] %}
{% set remaining = budget - spent %}
<div>
<dt>Budget </dt>
<dd>{{ budget | dollars }}</dd>
</div>
<div>
<dt>Remaining</dt>
<dd>{{ remaining | dollars }}</dd>
</div>
</dl>
</div>
<div>
<meter value='{{ spent }}' min='0' max='{{ budget }}' title='{{ spent | dollars }} Total spend to date'>
<div class='meter__fallback' style='width:{{ (spent / budget) * 100 if budget else 0 }}%;'></div>
</meter>
<dl class='spend-summary__spent'>
<dt>Total spend to date</dt>
<dd>{{ spent | dollars }}</dd>
</dl>
</div>
</div>
</div>
<div class='funding-summary-row__col'>
<div class='panel to-summary'>
<div class='to-summary__row'>
<div class='to-summary__to'>
<h2 class='to-summary__heading'>Task Order</h2>
<dl class='to-summary__to-number'>
<dt class='usa-sr-only'>Task Order Number</dt>
<dd>{{ legacy_task_order.number }}</dd>
</dl>
</div>
<div class='to-summary__expiration'>
<dl>
<div>
<dt>Expires</dt>
<dd>
{% if expiration_date %}
<local-datetime
timestamp='{{ expiration_date }}'
format='MMMM D, YYYY'>
</local-datetime>
{% else %}
-
{% endif %}
</dd>
</div>
<div>
<dt>Remaining</dt>
<dd>
{% if remaining_days is not none %}
{{ remaining_days }} days
{% else %}
-
{% endif %}
</dd>
</div>
</dl>
<a href='{{ url_for("workspaces.workspace", workspace_id=workspace.id) }}' class='icon-link'>
Manage Task Order
</a>
</div>
</div>
<dl class='to-summary__co'>
<dt>Contracting Officer</dt>
<dd>
{{ jedi_request.contracting_officer_full_name }}
<a class='icon-link' href='mailto:{{ jedi_request.contracting_officer_email }}'>{{ jedi_request.contracting_officer_email }}</a>
</dd>
</dl>
</div>
</div>
</div>
{% set workspace_totals = monthly_totals['workspace'] %}
{% set current_month_index = current_month.strftime('%m/%Y') %}
{% set prev_month_index = prev_month.strftime('%m/%Y') %}
{% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
{% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %}
{% if not workspace.applications %}
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_WORKSPACE) %}
{% set message = 'This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started.'
if can_create_applications
else 'This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments.'
%}
{{ EmptyState(
'Nothing to report',
action_label='Add a New Application' if can_create_applications else None,
action_href=url_for('workspaces.new_application', workspace_id=workspace.id) if can_create_applications else None,
icon='chart',
sub_message=message
) }}
{% else %}
<budget-chart
v-cloak
budget={{ budget }}
current-month='{{ current_month_index }}'
expiration-date='{{ expiration_date }}'
v-bind:months='{{ cumulative_budget.months | tojson }}'
inline-template>
<div class='budget-chart panel' ref='panel'>
<header class='budget-chart__header panel__heading panel__heading--tight'>
<h2 class='h3'>Cumulative Budget</h2>
<div class='budget-chart__legend'>
<dl class='budget-chart__legend__spend'>
<div>
<dt>Monthly Spend</dt>
<dd class='budget-chart__legend__dot monthly'><span class='usa-sr-only'>Monthly spend visual key</span></dd>
</div>
<div>
<dt>Accumulated Spend</dt>
<dd class='budget-chart__legend__dot accumulated'><span class='usa-sr-only'>Accumulated spend visual key</span></dd>
</div>
</dl>
<dl class='budget-chart__legend__projected'>
<div>
<dt>Projected</dt>
<dd>
<div class='budget-chart__legend__line spend'><span class='usa-sr-only'>Projected monthly spend visual key</span></div>
<div class='budget-chart__legend__line accumulated'><span class='usa-sr-only'>Projected accumulated spend visual key</span></div>
</dd>
</div>
</dl>
</div>
</header>
<svg v-bind:height='height' v-bind:width='width'>
<defs>
<filter x="-0.04" y="0" width="1.08" height="1" class='filter__text-background' id="text-background">
<feFlood/>
<feComposite in="SourceGraphic"/>
</filter>
</defs>
{# spend/projected budget path lines #}
<path class='budget-chart__projected-path' v-bind:d='projectedPath'></path>
<path class='budget-chart__spend-path' v-bind:d='spendPath'></path>
{# max budget line #}
<line
class='budget-chart__budget-line'
x1='0'
v-bind:x2='width'
v-bind:y1='budgetHeight'
v-bind:y2='budgetHeight'></line>
<g v-for='month in displayedMonths' >
{# make this clickable to focus on that month #}
<a v-bind:href='"{{ reports_url }}?month=" + month.date.monthIndex + "&year=" + month.date.year'>
<defs>
<filter
x="-0.04"
y="0"
width="1.08"
height="1"
class='filter__text-background'
v-bind:class='{ "filter__text-background--highlighted": month.isHighlighted }'
v-bind:id="'text-background__' +month.date.month + month.date.year">
<feFlood/>
<feComposite in="SourceGraphic"/>
</filter>
</defs>
<title>
<span v-html='month.date.month + " " + month.date.year'></span>&nbsp;|&nbsp;<!--
--><template v-if='month.cumulativeTotal'><!--
--><template v-if='month.budget && month.budget.spend'>Spend:</template><!--
--><template v-else>Projected Spend:</template><!--
--><span v-html='month.spendAmount'></span><!--
-->&nbsp;|&nbsp;<!--
--><template v-if='month.budget'>Total:</template><!--
--><template v-else>Projected Total:</template><!--
--><span v-html='month.cumulativeAmount'></span><!--
--></template><!--
--><template v-else>No spend for this month</template>
</title>
{# container block #}
<rect
class='budget-chart__block'
v-bind:class='{ "budget-chart__block--highlighted": month.isHighlighted, "budget-chart__block-is-expiration": month.isExpirationMonth }'
v-bind:width='month.metrics.blockWidth'
v-bind:x='month.metrics.blockX'
v-bind:height='height'></rect>
{# budget bar #}
<rect
v-if='month.budget'
class='budget-chart__bar'
v-bind:class='{ "budget-chart__bar--projected": month.budget.projected }'
v-bind:width='month.metrics.barWidth'
v-bind:height='month.metrics.barHeight'
v-bind:x='month.metrics.barX'
v-bind:y='month.metrics.barY'></rect>
{# projected budget bar #}
<rect
v-if='!month.budget'
class='budget-chart__bar budget-chart__bar--projected'
v-bind:width='month.metrics.barWidth'
v-bind:height='month.metrics.barHeight'
v-bind:x='month.metrics.barX'
v-bind:y='month.metrics.barY'></rect>
{# task order expiration line #}
<line
v-if='month.isExpirationMonth'
class='budget-chart__expiration-line'
v-bind:x1='month.metrics.cumulativeX'
v-bind:x2='month.metrics.cumulativeX'
y1='0'
v-bind:y2='baseHeight'></line>
{# task order expiration label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.isExpirationMonth'
text-anchor='middle'
v-bind:x='month.metrics.cumulativeX'
v-bind:y='budgetHeight + 20'
class='budget-chart__label'>T.O. Expires</text>
{# cumulative dot #}
<circle
v-if='month.cumulativeTotal'
class='budget-chart__cumulative__dot'
v-bind:r='month.metrics.cumulativeR'
v-bind:cx='month.metrics.cumulativeX'
v-bind:cy='month.metrics.cumulativeY'></circle>
{# abbreviated cumulative label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.cumulativeTotal'
v-bind:x='month.metrics.cumulativeX'
v-bind:y='month.metrics.cumulativeY - 10'
text-anchor='middle'
class='budget-chart__label'
v-html='month.abbreviatedCumulative'></text>
{# abbreviated spend label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 20'
text-anchor='middle'
class='budget-chart__label'
v-html='"+" + month.abbreviatedSpend'></text>
{# month label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 40'
text-anchor='middle'
class='budget-chart__label budget-chart__label--strong'
v-html='month.date.month'></text>
{# year label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.showYear'
v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 55'
text-anchor='middle'
class='budget-chart__label budget-chart__label--strong'
v-html='month.date.year'></text>
</g>
</a>
<text
x='20'
v-bind:y='budgetHeight + 20'
class='budget-chart__label'>Total Budget</text>
<text
x='20'
v-bind:y='budgetHeight + 40'
class='budget-chart__label'
v-html='displayBudget'></text>
</svg>
</div>
</budget-chart>
<div class='spend-table responsive-table-wrapper'>
<div class='spend-table__header'>
<h2 class='spend-table__title'>Total spend per month </h2>
<select name='month' id='month' onchange='location = this.value' class='spend-table__month-select'>
{% for m in cumulative_budget["months"] %}
{% set month = m | dateFromString %}
<option
{% if month.month == current_month.month and month.year == current_month.year %}
selected='selected'
{% endif %}
value='{{ url_for("workspaces.workspace_reports",
workspace_id=workspace.id,
month=month.month,
year=month.year) }}'
>
{{ month.strftime('%B %Y') }}
</option>
{% endfor %}
{% if not cumulative_budget["months"] %}
<option>{{ current_month.strftime('%B %Y') }}</option>
{% endif %}
</select>
</div>
<spend-table
v-bind:applications='{{ monthly_totals['applications'] | tojson }}'
v-bind:workspace='{{ workspace_totals | tojson }}'
v-bind:environments='{{ monthly_totals['environments'] | tojson }}'
current-month-index='{{ current_month_index }}'
prev-month-index='{{ prev_month_index }}'
two-months-ago-index='{{ two_months_ago_index }}'
inline-template>
<table>
<thead>
<th scope='col'><span class='usa-sr-only'>Spending scope</span></th>
<th scope='col' class='table-cell--align-right previous-month'>{{ two_months_ago.strftime('%B %Y') }}</th>
<th scope='col' class='table-cell--align-right previous-month'>{{ prev_month.strftime('%B %Y') }}</th>
<th scope='col' class='table-cell--align-right current-month'>{{ current_month.strftime('%B %Y') }}</th>
<th class='current-month'>% of total spend this month</th>
</thead>
<tbody class='spend-table__workspace'>
<tr>
<th scope='row'>Total</th>
<td class='table-cell--align-right previous-month'>{{ workspace_totals.get(two_months_ago_index, 0) | dollars }}</td>
<td class='table-cell--align-right previous-month'>{{ workspace_totals.get(prev_month_index, 0) | dollars }}</td>
<td class='table-cell--align-right current-month'>{{ workspace_totals.get(current_month_index, 0) | dollars }}</td>
<td class='table-cell--expand current-month meter-cell'>
<meter value='{{ workspace_totals.get(current_month_index, 0) }}' min='0' max='{{ workspace_totals.get(current_month_index, 0) }}'>
<div class='meter__fallback' style='width: 100%'></div>
</meter>
</td>
</tr>
</tbody>
<tbody v-for='(application, name) in applicationsState' class='spend-table__application'>
<tr>
<th scope='rowgroup'>
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__application__toggler'>
<template v-if='application.isVisible'>{{ Icon('caret_down') }}</template>
<template v-else>{{ Icon('caret_right') }}</template>
<span v-html='name'></span>
</button>
</th>
<td class='table-cell--align-right previous-month'>
<span v-html='formatDollars(application[twoMonthsAgoIndex] || 0)'></span>
</td>
<td class='table-cell--align-right previous-month'>
<span v-html='formatDollars(application[prevMonthIndex] || 0)'></span>
</td>
<td class='table-cell--align-right current-month'>
<span v-html='formatDollars(application[currentMonthIndex] || 0)'></span>
</td>
<td class='table-cell--expand current-month meter-cell'>
<span class='spend-table__meter-value'>
<span v-html='round( 100 * ((application[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%"'></span>
</span>
<meter v-bind:value='application[currentMonthIndex] || 0' min='0' v-bind:max='workspace[currentMonthIndex] || 1'>
<div class='meter__fallback' v-bind:style='"width:" + round( 100 * ((application[currentMonthIndex] || 0) / (workspace[currentMonthIndex] || 1) )) + "%;"'></div>
</meter>
</td>
</tr>
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='spend-table__application__env'>
<th scope='rowgroup'>
<a href='#' class='icon-link spend-table__application__env'>
{{ Icon('link') }}
<span v-html='envName'></span>
</a>
</th>
<td class='table-cell--align-right previous-month'>
<span v-html='formatDollars(environment[twoMonthsAgoIndex] || 0)'></span>
</td>
<td class='table-cell--align-right previous-month'>
<span v-html='formatDollars(environment[prevMonthIndex] || 0)'></span>
</td>
<td class='table-cell--align-right current-month'>
<span v-html='formatDollars(environment[currentMonthIndex] || 0)'></span>
</td>
<td class='table-cell--expand current-month'></td>
</tr>
</tbody>
</table>
</spend-table>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,30 +0,0 @@
{% from "components/empty_state.html" import EmptyState %}
{% extends "workspaces/base.html" %}
{% block workspace_content %}
{% if not workspace.task_orders %}
{{ EmptyState(
'This portfolio doesnt have any task orders yet.',
action_label='Add a New Task Order',
action_href=url_for('task_orders.new', screen=1, workspace_id=workspace.id),
icon='cloud',
) }}
{% else %}
<ul>
{% for task_order in workspace.task_orders %}
<li class='block-list__item'>
<a href='{{ url_for("workspaces.view_task_order", workspace_id=workspace.id, task_order_id=task_order.id)}}'>
<span>{{ task_order.start_date }} - {{ task_order.end_date }}</span>
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}