workspace -> portfolio everywhere
This commit is contained in:
@@ -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 %}
|
@@ -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 %}
|
@@ -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 doesn’t 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 %}
|
||||
|
@@ -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 %}
|
@@ -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 %}
|
@@ -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 %}
|
@@ -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 %}
|
||||
|
@@ -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 %}
|
@@ -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 %}
|
||||
|
@@ -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 %}
|
@@ -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> | <!--
|
||||
--><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><!--
|
||||
--> | <!--
|
||||
--><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 %}
|
@@ -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 doesn’t 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 %}
|
Reference in New Issue
Block a user