Merge branch 'master' into revert-user-deletion
This commit is contained in:
commit
96c1fcbe85
@ -135,6 +135,67 @@ jobs:
|
|||||||
- run: "docker tag ${AZURE_SERVER_NAME}/atat:atat-${CIRCLE_SHA1} ${AZURE_SERVER_NAME}/atat:latest"
|
- run: "docker tag ${AZURE_SERVER_NAME}/atat:atat-${CIRCLE_SHA1} ${AZURE_SERVER_NAME}/atat:latest"
|
||||||
- run: "docker push ${AZURE_SERVER_NAME}/atat:latest"
|
- run: "docker push ${AZURE_SERVER_NAME}/atat:latest"
|
||||||
|
|
||||||
|
integration-tests:
|
||||||
|
docker:
|
||||||
|
- image: docker:17.05.0-ce-git
|
||||||
|
steps:
|
||||||
|
- setup_remote_docker:
|
||||||
|
version: 18.06.0-ce
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Set up temporary docker network
|
||||||
|
command: docker network create atat
|
||||||
|
- run:
|
||||||
|
name: Build image
|
||||||
|
command: docker build . -t atat:latest
|
||||||
|
- run:
|
||||||
|
name: Get storage containers
|
||||||
|
command: docker pull postgres:latest && docker pull redis:latest
|
||||||
|
- run:
|
||||||
|
name: Start redis
|
||||||
|
command: docker run -d --network atat --link redis:redis -p 6379:6379 --name redis redis:latest
|
||||||
|
- run:
|
||||||
|
name: Start postgres
|
||||||
|
command: docker run -d --network atat --link postgres:postgres -p 5432:5432 --name postgres postgres:latest
|
||||||
|
- run:
|
||||||
|
name: Start application container
|
||||||
|
command: |
|
||||||
|
docker run -d \
|
||||||
|
-e DISABLE_CRL_CHECK=true \
|
||||||
|
-e PGHOST=postgres \
|
||||||
|
-e REDIS_URI=redis://redis:6379 \
|
||||||
|
-p 8000:8000 \
|
||||||
|
--network atat \
|
||||||
|
--name test-atat \
|
||||||
|
atat:latest \
|
||||||
|
uwsgi \
|
||||||
|
--callable app \
|
||||||
|
--module app \
|
||||||
|
--plugin python3 \
|
||||||
|
--virtualenv /opt/atat/atst/.venv \
|
||||||
|
--http-socket :8000
|
||||||
|
- run:
|
||||||
|
name: Wait for containers
|
||||||
|
command: sleep 3
|
||||||
|
- run:
|
||||||
|
name: Create database
|
||||||
|
command: docker exec postgres createdb -U postgres atat
|
||||||
|
- run:
|
||||||
|
name: Apply migrations
|
||||||
|
command: docker exec test-atat .venv/bin/python .venv/bin/alembic upgrade head
|
||||||
|
- run:
|
||||||
|
name: Execute Ghost Inspector test suite
|
||||||
|
command: |
|
||||||
|
docker pull ghostinspector/test-runner-standalone:latest
|
||||||
|
docker run \
|
||||||
|
-e NGROK_TOKEN=$NGROK_TOKEN \
|
||||||
|
-e GI_API_KEY=$GI_API_KEY \
|
||||||
|
-e GI_SUITE=$GI_SUITE \
|
||||||
|
-e GI_PARAMS_JSON='{}' \
|
||||||
|
-e APP_PORT="test-atat:8000" \
|
||||||
|
--network atat \
|
||||||
|
ghostinspector/test-runner-standalone:latest
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
run-tests:
|
run-tests:
|
||||||
@ -143,9 +204,12 @@ workflows:
|
|||||||
- test:
|
- test:
|
||||||
requires:
|
requires:
|
||||||
- app_setup
|
- app_setup
|
||||||
- azure-build-and-push-image:
|
- integration-tests:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test
|
||||||
|
- azure-build-and-push-image:
|
||||||
|
requires:
|
||||||
|
- integration-tests
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
@ -204,7 +268,7 @@ workflows:
|
|||||||
repo: atat
|
repo: atat
|
||||||
tag: "atat-${CIRCLE_SHA1},latest"
|
tag: "atat-${CIRCLE_SHA1},latest"
|
||||||
requires:
|
requires:
|
||||||
- test
|
- integration-tests
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -52,10 +52,12 @@ ssl/client-certs/*.srl
|
|||||||
# je coverage output
|
# je coverage output
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
# BrowserStack
|
||||||
# selenium testing
|
|
||||||
browserstacklocal
|
browserstacklocal
|
||||||
|
|
||||||
|
# decompiled CircleCI yaml
|
||||||
|
local-ci.yml
|
||||||
|
|
||||||
# python config
|
# python config
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"files": "^.secrets.baseline$",
|
"files": "^.secrets.baseline$",
|
||||||
"lines": null
|
"lines": null
|
||||||
},
|
},
|
||||||
"generated_at": "2019-10-03T14:34:50Z",
|
"generated_at": "2019-10-14T19:14:26Z",
|
||||||
"plugins_used": [
|
"plugins_used": [
|
||||||
{
|
{
|
||||||
"base64_limit": 4.5,
|
"base64_limit": 4.5,
|
||||||
@ -40,6 +40,13 @@
|
|||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 156,
|
"line_number": 156,
|
||||||
"type": "Secret Keyword"
|
"type": "Secret Keyword"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hashed_secret": "81b127e2222d9bfc4609053faec85300f7525463",
|
||||||
|
"is_secret": false,
|
||||||
|
"is_verified": false,
|
||||||
|
"line_number": 244,
|
||||||
|
"type": "Secret Keyword"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"alembic.ini": [
|
"alembic.ini": [
|
||||||
@ -153,24 +160,6 @@
|
|||||||
"type": "Private Key"
|
"type": "Private Key"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tests/acceptance/conftest.py": [
|
|
||||||
{
|
|
||||||
"hashed_secret": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
|
|
||||||
"is_secret": false,
|
|
||||||
"is_verified": false,
|
|
||||||
"line_number": 48,
|
|
||||||
"type": "Basic Auth Credentials"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests/fixtures/chain/make-chain.sh": [
|
|
||||||
{
|
|
||||||
"hashed_secret": "bad2e396920ce37fe53fc291f90b130d915375fb",
|
|
||||||
"is_secret": false,
|
|
||||||
"is_verified": false,
|
|
||||||
"line_number": 35,
|
|
||||||
"type": "Secret Keyword"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests/forms/test_validators.py": [
|
"tests/forms/test_validators.py": [
|
||||||
{
|
{
|
||||||
"hashed_secret": "260408f687da9094705a841acda9b029563053ee",
|
"hashed_secret": "260408f687da9094705a841acda9b029563053ee",
|
||||||
@ -194,7 +183,7 @@
|
|||||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||||
"is_secret": false,
|
"is_secret": false,
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 651,
|
"line_number": 656,
|
||||||
"type": "Hex High Entropy String"
|
"type": "Hex High Entropy String"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
37
README.md
37
README.md
@ -220,30 +220,37 @@ To generate coverage reports for the Javascript tests:
|
|||||||
|
|
||||||
yarn test:coverage
|
yarn test:coverage
|
||||||
|
|
||||||
### Selenium Tests
|
### Ghost Inspector Tests
|
||||||
|
|
||||||
Selenium tests rely on BrowserStack. In order to run the Selenium tests
|
AT-AT uses [Ghost Inpsector](https://app.ghostinspector.com/) for
|
||||||
locally, you need BrowserStack credentials. The user email and key can
|
integration testing. These tests do not run locally as part of the
|
||||||
be found on the account settings page. To run the selenium tests:
|
regular test suite but do run in CI. To run them locally, you will
|
||||||
|
need the following:
|
||||||
|
|
||||||
|
- [docker](https://docs.docker.com/v17.12/install/)
|
||||||
|
- [circleci CLI tool](https://circleci.com/docs/2.0/local-cli/#installation)
|
||||||
|
- the prerequisite variable information listed [here](https://ghostinspector.com/docs/integration/circle-ci/)
|
||||||
|
|
||||||
|
The version of our CircleCI config (2.1) is incompatible with the
|
||||||
|
`circleci` tool. First run:
|
||||||
|
|
||||||
```
|
```
|
||||||
BROWSERSTACK_TOKEN=<token> BROWSERSTACK_EMAIL=<email> ./script/selenium_test
|
circleci config process .circleci/config.yml > local-ci.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
The selenium tests are in `tests/acceptance`. This directory is ignored by
|
Then run the job:
|
||||||
pytest for normal test runs.
|
|
||||||
|
|
||||||
The `selenium_test` script manages the setup of a separate database and
|
|
||||||
launching the BrowserStackLocal client. If you already have the client running
|
|
||||||
locally, you can run the selenium tests with:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
BROWSERSTACK_TOKEN=<token> BROWSERSTACK_EMAIL=<email> pipenv run pytest tests/acceptance
|
circleci local execute -e GI_SUITE=<SUITE_ID> -e GI_API_KEY=<API KEY> -e NGROK_TOKEN=<NGROK TOKEN> --job integration-tests -c local-ci.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
The BrowserStack email is the one associated with the account. The token is
|
If the job fails and you want to re-run it, you may receive errors
|
||||||
available in the BrowserStack profile information page. Go to the dashboard,
|
about running docker containers or the network already existing.
|
||||||
then "Account" > "Settings", then the token is under "Local Testing".
|
Some version of the following should reset your local docker state:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker container stop redis postgres test-atat; docker container rm redis postgres test-atat ; docker network rm atat
|
||||||
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
@ -142,3 +142,9 @@ class PortfolioInvitations(BaseInvitations):
|
|||||||
class ApplicationInvitations(BaseInvitations):
|
class ApplicationInvitations(BaseInvitations):
|
||||||
model = ApplicationInvitation
|
model = ApplicationInvitation
|
||||||
role_domain_class = ApplicationRoles
|
role_domain_class = ApplicationRoles
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def revoke(cls, token):
|
||||||
|
invite = super().revoke(token)
|
||||||
|
ApplicationRoles.disable(invite.role)
|
||||||
|
return invite
|
||||||
|
@ -183,11 +183,7 @@ def handle_create_member(application_id, form_data):
|
|||||||
token=invite.token,
|
token=invite.token,
|
||||||
)
|
)
|
||||||
|
|
||||||
flash(
|
flash("new_application_member", user_name=invite.first_name)
|
||||||
"new_application_member",
|
|
||||||
user_name=invite.user_name,
|
|
||||||
application_name=application.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
except AlreadyExistsError:
|
except AlreadyExistsError:
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -8,9 +8,9 @@ MESSAGES = {
|
|||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
"application_created": {
|
"application_created": {
|
||||||
"title_template": translate("flash.success"),
|
"title_template": translate("flash.application.created.title"),
|
||||||
"message_template": """
|
"message_template": """
|
||||||
{{ "flash.application.created" | translate({"application_name": application_name}) }}
|
{{ "flash.application.created.message" | translate({"application_name": application_name}) }}
|
||||||
""",
|
""",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
@ -40,7 +40,7 @@ MESSAGES = {
|
|||||||
"category": "error",
|
"category": "error",
|
||||||
},
|
},
|
||||||
"application_invite_resent": {
|
"application_invite_resent": {
|
||||||
"title_template": "Application invitation revoked",
|
"title_template": "Application invitation resent",
|
||||||
"message_template": "You have successfully resent the invite for {{ user_name }} from {{ application_name }}",
|
"message_template": "You have successfully resent the invite for {{ user_name }} from {{ application_name }}",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
@ -104,9 +104,9 @@ MESSAGES = {
|
|||||||
"category": "warning",
|
"category": "warning",
|
||||||
},
|
},
|
||||||
"new_application_member": {
|
"new_application_member": {
|
||||||
"title_template": translate("flash.success"),
|
"title_template": """{{ "flash.new_application_member.title" | translate({ "user_name": user_name }) }}""",
|
||||||
"message_template": """
|
"message_template": """
|
||||||
<p>{{ "flash.new_application_member" | translate({ "user_name": user_name, "application_name": application_name }) }}</p>
|
<p>{{ "flash.new_application_member.message" | translate({ "user_name": user_name }) }}</p>
|
||||||
""",
|
""",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,10 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
textinput,
|
textinput,
|
||||||
},
|
},
|
||||||
|
created: function() {
|
||||||
|
this.$root.$on('field-change', this.handleFieldChange)
|
||||||
|
if (this.initialData) this.changed = true
|
||||||
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,13 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="list-alt" class="svg-inline--fa fa-list-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 480H48c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v352c0 26.51-21.49 48-48 48zM128 120c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zm0 96c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zm0 96c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zm288-136v-32c0-6.627-5.373-12-12-12H204c-6.627 0-12 5.373-12 12v32c0 6.627 5.373 12 12 12h200c6.627 0 12-5.373 12-12zm0 96v-32c0-6.627-5.373-12-12-12H204c-6.627 0-12 5.373-12 12v32c0 6.627 5.373 12 12 12h200c6.627 0 12-5.373 12-12zm0 96v-32c0-6.627-5.373-12-12-12H204c-6.627 0-12 5.373-12 12v32c0 6.627 5.373 12 12 12h200c6.627 0 12-5.373 12-12z"></path></svg>
|
||||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
|
|
||||||
<title>Combined Shape</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="Portfolio-Nav-/-Applications" transform="translate(-26.000000, 0.000000)" fill="#0071BC">
|
|
||||||
<g id="Group-4">
|
|
||||||
<path d="M41,30 C32.7157288,30 26,23.2842712 26,15 C26,6.71572875 32.7157288,0 41,0 C49.2842712,0 56,6.71572875 56,15 C56,23.2842712 49.2842712,30 41,30 Z M38.7142901,17.9107118 C38.7142901,17.5262247 38.4023478,17.2142824 38.0178607,17.2142824 L35.6964294,17.2142824 C35.3119423,17.2142824 35,17.5262247 35,17.9107118 L35,19.3035706 C35,19.6880577 35.3119423,20 35.6964294,20 L38.0178607,20 C38.4023478,20 38.7142901,19.6880577 38.7142901,19.3035706 L38.7142901,17.9107118 Z M38.7142901,14.1964217 C38.7142901,13.8119346 38.4023478,13.4999923 38.0178607,13.4999923 L35.6964294,13.4999923 C35.3119423,13.4999923 35,13.8119346 35,14.1964217 L35,15.5892805 C35,15.9737675 35.3119423,16.2857099 35.6964294,16.2857099 L38.0178607,16.2857099 C38.4023478,16.2857099 38.7142901,15.9737675 38.7142901,15.5892805 L38.7142901,14.1964217 Z M48.0000155,17.9107118 C48.0000155,17.5262247 47.6880732,17.2142824 47.3035861,17.2142824 L40.3392921,17.2142824 C39.954805,17.2142824 39.6428627,17.5262247 39.6428627,17.9107118 L39.6428627,19.3035706 C39.6428627,19.6880577 39.954805,20 40.3392921,20 L47.3035861,20 C47.6880732,20 48.0000155,19.6880577 48.0000155,19.3035706 L48.0000155,17.9107118 Z M38.7142901,10.4821315 C38.7142901,10.0976444 38.4023478,9.78570211 38.0178607,9.78570211 L35.6964294,9.78570211 C35.3119423,9.78570211 35,10.0976444 35,10.4821315 L35,11.8749903 C35,12.2594774 35.3119423,12.5714197 35.6964294,12.5714197 L38.0178607,12.5714197 C38.4023478,12.5714197 38.7142901,12.2594774 38.7142901,11.8749903 L38.7142901,10.4821315 Z M48.0000155,14.1964217 C48.0000155,13.8119346 47.6880732,13.4999923 47.3035861,13.4999923 L40.3392921,13.4999923 C39.954805,13.4999923 39.6428627,13.8119346 39.6428627,14.1964217 L39.6428627,15.5892805 C39.6428627,15.9737675 39.954805,16.2857099 40.3392921,16.2857099 L47.3035861,16.2857099 C47.6880732,16.2857099 48.0000155,15.9737675 48.0000155,15.5892805 L48.0000155,14.1964217 Z M48.0000155,10.4821315 C48.0000155,10.0976444 47.6880732,9.78570211 47.3035861,9.78570211 L40.3392921,9.78570211 C39.954805,9.78570211 39.6428627,10.0976444 39.6428627,10.4821315 L39.6428627,11.8749903 C39.6428627,12.2594774 39.954805,12.5714197 40.3392921,12.5714197 L47.3035861,12.5714197 C47.6880732,12.5714197 48.0000155,12.2594774 48.0000155,11.8749903 L48.0000155,10.4821315 Z" id="Combined-Shape"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 940 B |
@ -1,13 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-invoice-dollar" class="svg-inline--fa fa-file-invoice-dollar fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M377 105L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-153 31V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zM64 72c0-4.42 3.58-8 8-8h80c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8H72c-4.42 0-8-3.58-8-8V72zm0 80v-16c0-4.42 3.58-8 8-8h80c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8H72c-4.42 0-8-3.58-8-8zm144 263.88V440c0 4.42-3.58 8-8 8h-16c-4.42 0-8-3.58-8-8v-24.29c-11.29-.58-22.27-4.52-31.37-11.35-3.9-2.93-4.1-8.77-.57-12.14l11.75-11.21c2.77-2.64 6.89-2.76 10.13-.73 3.87 2.42 8.26 3.72 12.82 3.72h28.11c6.5 0 11.8-5.92 11.8-13.19 0-5.95-3.61-11.19-8.77-12.73l-45-13.5c-18.59-5.58-31.58-23.42-31.58-43.39 0-24.52 19.05-44.44 42.67-45.07V232c0-4.42 3.58-8 8-8h16c4.42 0 8 3.58 8 8v24.29c11.29.58 22.27 4.51 31.37 11.35 3.9 2.93 4.1 8.77.57 12.14l-11.75 11.21c-2.77 2.64-6.89 2.76-10.13.73-3.87-2.43-8.26-3.72-12.82-3.72h-28.11c-6.5 0-11.8 5.92-11.8 13.19 0 5.95 3.61 11.19 8.77 12.73l45 13.5c18.59 5.58 31.58 23.42 31.58 43.39 0 24.53-19.05 44.44-42.67 45.07z"></path></svg>
|
||||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
|
|
||||||
<title>Shape</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="Portfolio-Nav-/-Funding" transform="translate(-12.000000, 0.000000)" fill="#0071BC" fill-rule="nonzero">
|
|
||||||
<g id="Group">
|
|
||||||
<path d="M27,0 C18.7333333,0 12,6.73333333 12,15 C12,23.2666667 18.7333333,30 27,30 C35.2666667,30 42,23.2666667 42,15 C42,6.73333333 35.2666667,0 27,0 Z M29.5138284,20.9349809 C28.9570383,21.2107813 28.2731789,21.325742 27.589873,21.4089879 L27.5500151,23.6924464 C27.5406042,24.2315964 27.0789776,24.6359554 26.559629,24.6268901 C26.0078211,24.6172583 25.5936005,24.1658869 25.6024578,23.6584517 L25.6428693,21.3432785 C24.6718585,21.1677076 23.7360749,20.8341298 22.9664629,20.281382 C22.5175686,19.9563029 22.4307088,19.3520238 22.7624983,18.9453985 C23.0948413,18.5070584 23.7132286,18.4226793 24.1296635,18.7471919 C25.3157105,19.6244522 27.3903371,19.8192868 28.6343082,19.2382375 C29.354502,18.9018405 29.6890593,18.3366417 29.7028989,17.5437741 C29.7167384,16.7509066 28.910792,16.4195951 26.6463919,15.9359287 C24.5437347,15.48681 21.6967281,14.8978011 21.7459969,12.0751927 C21.7731225,10.5211723 22.5737351,9.29789683 24.0135691,8.65681758 C24.5698056,8.41273181 25.1893,8.2649233 25.839593,8.21282548 L25.8683793,6.563661 C25.8777902,6.02451107 26.3394168,5.62015206 26.8587654,5.62921732 C27.4105733,5.63884917 27.8247939,6.09022051 27.8159366,6.59765574 L27.7860432,8.31024962 C28.8544318,8.48752024 29.8534732,8.9173753 30.6544374,9.53411911 C31.0708723,9.85863165 31.1252729,10.4623441 30.7929298,10.9006842 C30.4611404,11.3073095 29.8427531,11.3916886 29.3944125,11.0348948 C28.3074041,10.06419 26.1695197,9.77307822 24.86063,10.3529944 C24.107977,10.6888248 23.7409603,11.253457 23.7265672,12.0780393 C23.7071918,13.1880538 24.7722589,13.5556127 27.0691183,14.0398456 C29.1393163,14.4883978 31.6952964,15.008878 31.6510098,17.5460542 C31.6568972,19.0689264 30.8892974,20.2610537 29.5138284,20.9349809 Z" id="Shape"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,4 +1,5 @@
|
|||||||
.app-footer {
|
.app-footer {
|
||||||
|
z-index: 3;
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
border-top: 1px solid $color-gray-lightest;
|
border-top: 1px solid $color-gray-lightest;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
.global-navigation {
|
.global-navigation {
|
||||||
|
z-index: 2;
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
margin-bottom: -$footer-height * 2.5;
|
||||||
|
|
||||||
.sidenav__link {
|
.sidenav__link {
|
||||||
padding-right: $gap * 2;
|
padding-right: $gap * 2;
|
||||||
|
@ -29,9 +29,15 @@
|
|||||||
&__name {
|
&__name {
|
||||||
@include h1;
|
@include h1;
|
||||||
h1 {
|
h1 {
|
||||||
margin: ($gap * 2) $gap ($gap * 2) $gap;
|
margin: 0 $gap ($gap * 2) 0;
|
||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $small-font-size;
|
||||||
|
margin: 0 0 (-$gap * 0.5);
|
||||||
|
color: $color-gray-medium;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__budget {
|
&__budget {
|
||||||
@ -69,17 +75,26 @@
|
|||||||
font-size: $small-font-size;
|
font-size: $small-font-size;
|
||||||
|
|
||||||
.icon-link {
|
.icon-link {
|
||||||
padding: 0.8rem 1.6rem;
|
padding: $gap;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $color-blue-darkest;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
color: $color-gray;
|
|
||||||
opacity: 0.3;
|
|
||||||
.icon {
|
.icon {
|
||||||
@include icon-color($color-gray);
|
@include icon-color($color-blue-darkest);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
opacity: 1;
|
|
||||||
color: $color-blue;
|
color: $color-blue;
|
||||||
|
background-color: $color-gray-lightest;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@include icon-color($color-blue);
|
@include icon-color($color-blue);
|
||||||
}
|
}
|
||||||
@ -121,7 +136,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.portfolio-content {
|
.portfolio-content {
|
||||||
margin: 1 * $gap $gap 0 $gap;
|
margin: (4 * $gap) $gap 0 $gap;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
@include shadow-panel;
|
@include shadow-panel;
|
||||||
@ -133,11 +148,6 @@
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subheading {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: $color-gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-table-wrapper {
|
.responsive-table-wrapper {
|
||||||
padding-bottom: $gap * 3;
|
padding-bottom: $gap * 3;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@ -219,6 +229,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.application-content {
|
.application-content {
|
||||||
|
margin-top: $gap * 4;
|
||||||
|
|
||||||
.subheading {
|
.subheading {
|
||||||
@include subheading;
|
@include subheading;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -247,7 +259,6 @@
|
|||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
max-height: 9rem;
|
max-height: 9rem;
|
||||||
max-width: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel__footer {
|
.panel__footer {
|
||||||
|
@ -44,4 +44,9 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-right: $spacing-small;
|
padding-right: $spacing-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.col--half {
|
||||||
|
width: 50%;
|
||||||
|
max-width: 30em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-group-footer {
|
||||||
|
@extend .action-group;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-top: $gap;
|
||||||
|
padding-bottom: $gap;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
bottom: $footer-height;
|
||||||
|
background: white;
|
||||||
|
right: 0;
|
||||||
|
padding-right: $gap * 4;
|
||||||
|
border-top: 1px solid $color-gray-light;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
@ -57,13 +57,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.usa-input {
|
.usa-input {
|
||||||
margin: ($gap * 4) ($gap * 2) ($gap * 4) 0;
|
margin: ($gap * 2) 0;
|
||||||
max-width: 75rem;
|
max-width: 75rem;
|
||||||
|
|
||||||
@include media($medium-screen) {
|
|
||||||
margin: ($gap * 4) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
padding: 0 0 ($gap / 2) 0;
|
padding: 0 0 ($gap / 2) 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -363,6 +359,8 @@ select {
|
|||||||
margin-right: $gap * 4;
|
margin-right: $gap * 4;
|
||||||
|
|
||||||
.usa-input {
|
.usa-input {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
input,
|
input,
|
||||||
label,
|
label,
|
||||||
.usa-input__message {
|
.usa-input__message {
|
||||||
@ -379,6 +377,8 @@ select {
|
|||||||
margin-left: $gap * 4;
|
margin-left: $gap * 4;
|
||||||
|
|
||||||
.usa-input {
|
.usa-input {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
max-width: 12rem;
|
max-width: 12rem;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.usa-input {
|
.usa-input {
|
||||||
margin: 0 ($gap * 4) 0 0;
|
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +27,10 @@
|
|||||||
.form-content--app-mem {
|
.form-content--app-mem {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
.modal__body {
|
||||||
|
min-width: 75rem;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="checkbox"] + label::before {
|
input[type="checkbox"] + label::before {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
@ -58,11 +61,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.application-member__user-info {
|
||||||
margin-top: 0;
|
|
||||||
|
|
||||||
.usa-input {
|
.usa-input {
|
||||||
margin: 0;
|
|
||||||
width: 45rem;
|
width: 45rem;
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% block portfolio_header %}
|
{% block portfolio_header %}
|
||||||
{% include "portfolios/header.html" %}
|
{% include "portfolios/header.html" %}
|
||||||
{% if application %}
|
{% if application %}
|
||||||
{{ StickyCTA(text=application.name, return_link_url=url_for('applications.portfolio_applications', portfolio_id=application.portfolio_id), return_link_text="BACK TO APPLICATIONS") }}
|
{{ StickyCTA(text=application.name) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -52,20 +52,12 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro InfoFields(member_form) %}
|
{% macro InfoFields(member_form) %}
|
||||||
<div class='form-row'>
|
<div class="application-member__user-info">
|
||||||
{{ TextInput(member_form.first_name, validation='requiredField', optional=False) }}
|
{{ TextInput(member_form.first_name, validation='requiredField', optional=False) }}
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.last_name, validation='requiredField', optional=False) }}
|
{{ TextInput(member_form.last_name, validation='requiredField', optional=False) }}
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.email, validation='email', optional=False) }}
|
{{ TextInput(member_form.email, validation='email', optional=False) }}
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
{{ PhoneInput(member_form.phone_number, member_form.phone_ext)}}
|
{{ PhoneInput(member_form.phone_number, member_form.phone_ext)}}
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.dod_id, validation='dodId', optional=False) }}
|
{{ TextInput(member_form.dod_id, validation='dodId', optional=False) }}
|
||||||
|
<a href="#">How do I find the DoD ID?</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="#">How do I find the DoD ID?</a>
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@ -23,29 +23,28 @@
|
|||||||
|
|
||||||
<application-name-and-description inline-template v-bind:initial-data='{{ form.data|tojson }}'>
|
<application-name-and-description inline-template v-bind:initial-data='{{ form.data|tojson }}'>
|
||||||
<form method="POST" action="{{ action }}" v-on:submit="handleSubmit">
|
<form method="POST" action="{{ action }}" v-on:submit="handleSubmit">
|
||||||
<div class="panel__content">
|
{{ form.csrf_token }}
|
||||||
{{ form.csrf_token }}
|
<div class="form-row">
|
||||||
<div class="form-row">
|
<div class="form-col">
|
||||||
<div class="form-col form-col--two-thirds">
|
{{ TextInput(form.name, optional=False) }}
|
||||||
{{ TextInput(form.name, optional=False) }}
|
{{ ('portfolios.applications.new.step_1_form_help_text.name' | translate | safe) }}
|
||||||
{{ ('portfolios.applications.new.step_1_form_help_text.description' | translate | safe) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-col form-col--two-thirds">
|
|
||||||
{{ TextInput(form.description, paragraph=True, optional=True) }}
|
|
||||||
{{ ('portfolios.applications.new.step_1_form_help_text.description' | translate | safe) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="panel__break">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-col form-col--two-thirds">
|
||||||
|
{{ TextInput(form.description, paragraph=True, optional=True) }}
|
||||||
|
{{ ('portfolios.applications.new.step_1_form_help_text.description' | translate | safe) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<span class="action-group">
|
<span class="action-group-footer">
|
||||||
{% block next_button %}
|
{% block next_button %}
|
||||||
{{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
|
{{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
<button disabled class="usa-button usa-button-secondary">Previous</button>
|
||||||
|
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
||||||
Cancel
|
Cancel
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -16,66 +16,63 @@
|
|||||||
|
|
||||||
{% set modalName = "newApplicationConfirmation" %}
|
{% set modalName = "newApplicationConfirmation" %}
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
<div class="panel__content">
|
<p>
|
||||||
<p>
|
{{ 'portfolios.applications.new.step_2_description' | translate }}
|
||||||
{{ 'portfolios.applications.new.step_2_description' | translate }}
|
</p>
|
||||||
</p>
|
<hr class="panel__break">
|
||||||
<hr>
|
<application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'>
|
||||||
<application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'>
|
<form method="POST" action="{{ url_for('applications.update_new_application_step_2', portfolio_id=portfolio.id, application_id=application.id) }}" v-on:submit="handleSubmit">
|
||||||
<form method="POST" action="{{ url_for('applications.update_new_application_step_2', portfolio_id=portfolio.id, application_id=application.id) }}" v-on:submit="handleSubmit">
|
<div class="subheading">{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
||||||
<div class="subheading">{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
<div class="panel">
|
||||||
<div class="panel">
|
<div class="panel__content">
|
||||||
<div class="panel__content">
|
{{ form.csrf_token }}
|
||||||
{{ form.csrf_token }}
|
<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">
|
{{ Alert(message=None, level="error", vue_template=True) }}
|
||||||
{{ Alert(message=None, level="error", vue_template=True) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="application-list-item">
|
|
||||||
<ul>
|
|
||||||
<li v-for="(environment, i) in environments" class="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" @input="onInput" placeholder="e.g. Development, Staging, Production"/> <input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
|
|
||||||
</div>
|
|
||||||
<div class="application-edit__env-list-item-block">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="block-list__footer">
|
|
||||||
<button
|
|
||||||
v-on:click="addEnvironment"
|
|
||||||
class="icon-link"
|
|
||||||
tabindex="0"
|
|
||||||
type="button">
|
|
||||||
{{ 'portfolios.applications.add_another_environment' | translate }}
|
|
||||||
{{ Icon("plus") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="application-list-item">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(environment, i) in environments" class="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" @input="onInput" placeholder="e.g. Development, Staging, Production"/> <input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="application-edit__env-list-item-block">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="block-list__footer">
|
||||||
|
<button
|
||||||
|
v-on:click="addEnvironment"
|
||||||
|
class="icon-link"
|
||||||
|
tabindex="0"
|
||||||
|
type="button">
|
||||||
|
{{ 'portfolios.applications.add_another_environment' | translate }}
|
||||||
|
{{ Icon("plus") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<span class="action-group">
|
<span class="action-group-footer">
|
||||||
{% block next_button %}
|
{% block next_button %}
|
||||||
{{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
|
{{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_1', application_id=application.id) }}">
|
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_1', application_id=application.id) }}">
|
||||||
Previous
|
Previous
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
||||||
Cancel
|
Cancel
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</application-environments>
|
</application-environments>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -11,31 +11,30 @@
|
|||||||
|
|
||||||
{% block application_content %}
|
{% block application_content %}
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
<div class="panel__content">
|
<p>
|
||||||
<p>
|
{{ ('portfolios.applications.new.step_3_description' | translate) }}
|
||||||
{{ ('portfolios.applications.new.step_3_description' | translate) }}
|
</p>
|
||||||
</p>
|
<hr class="panel__break">
|
||||||
<hr>
|
|
||||||
|
|
||||||
{{ MemberManagementTemplate(
|
{{ MemberManagementTemplate(
|
||||||
application,
|
application,
|
||||||
members,
|
members,
|
||||||
new_member_form,
|
new_member_form,
|
||||||
"applications.update_new_application_step_3",
|
"applications.update_new_application_step_3",
|
||||||
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
|
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
|
||||||
|
|
||||||
|
|
||||||
<span class="action-group">
|
<span class="action-group-footer">
|
||||||
<a class="usa-button" href="{{ url_for('applications.settings', application_id=application_id) }}">
|
<a class="usa-button" href="{{ url_for('applications.settings', application_id=application_id) }}">
|
||||||
Return to Application Settings
|
Return to Application Settings
|
||||||
</a>
|
</a>
|
||||||
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_2', application_id=application.id) }}">
|
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_2', application_id=application.id) }}">
|
||||||
Previous
|
Previous
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
|
||||||
Cancel
|
Cancel
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -19,58 +19,40 @@
|
|||||||
|
|
||||||
{% block application_content %}
|
{% block application_content %}
|
||||||
|
|
||||||
<div class='subheading'>{{ 'portfolios.applications.settings.name_description' | translate }}</div>
|
<h3>{{ 'portfolios.applications.settings.name_description' | translate }}</h3>
|
||||||
|
|
||||||
{% if user_can(permissions.EDIT_APPLICATION) %}
|
{% if user_can(permissions.EDIT_APPLICATION) %}
|
||||||
<base-form inline-template>
|
<base-form inline-template>
|
||||||
<form method="POST" action="{{ url_for('applications.update', application_id=application.id) }}">
|
<form method="POST" action="{{ url_for('applications.update', application_id=application.id) }}" class="col col--half">
|
||||||
<div class="panel">
|
{{ application_form.csrf_token }}
|
||||||
<div class="panel__content">
|
{{ TextInput(application_form.name, optional=False) }}
|
||||||
{{ application_form.csrf_token }}
|
{{ TextInput(application_form.description, paragraph=True, optional=True, showOptional=False) }}
|
||||||
<div class="form-row">
|
<div class="action-group action-group--tight">
|
||||||
<div class="form-col form-col--two-thirds">
|
{{ SaveButton('common.save_changes'|translate) }}
|
||||||
{{ TextInput(application_form.name, optional=False) }}
|
|
||||||
{{ TextInput(application_form.description, paragraph=True, optional=True) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel__footer">
|
|
||||||
<div class="action-group">
|
|
||||||
{{ SaveButton('common.save_changes'|translate) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="panel">
|
<!-- TODO: use new spacing styling to add in bottom margin here -->
|
||||||
<div class="panel__content">
|
<div class="">
|
||||||
<p>
|
<p>
|
||||||
{{ "fragments.edit_application_form.explain" | translate }}
|
{{ "fragments.edit_application_form.explain" | translate }}
|
||||||
</p>
|
</p>
|
||||||
<div class="form-row">
|
<div class="usa-input usa-input__title__view-only">
|
||||||
<div class="form-col">
|
{{ application_form.name.label() }}
|
||||||
<div class="usa-input usa-input__title__view-only">
|
|
||||||
{{ application_form.name.label() }}
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{{ application_form.name.data }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-col">
|
|
||||||
<div class="usa-input usa-input__title__view-only">
|
|
||||||
{{ application_form.description.label() }}
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{{ application_form.description.data }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ application_form.name.data }}
|
||||||
|
</p>
|
||||||
|
<div class="usa-input usa-input__title__view-only">
|
||||||
|
{{ application_form.description.label() }}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ application_form.description.data }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
|
||||||
{{ MemberManagementTemplate(
|
{{ MemberManagementTemplate(
|
||||||
application,
|
application,
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
{% if vue_template %}
|
{% if vue_template %}
|
||||||
<h3 class='usa-alert-heading' v-html='title'></h3>
|
<h3 class='usa-alert-heading' v-html='title'></h3>
|
||||||
{% elif title %}
|
{% elif title %}
|
||||||
<h3 class='usa-alert-heading'>{{title}}</h3>
|
<h3 class='usa-alert-heading'>{{ title | safe }}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if message %}
|
{% if message %}
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
{% from 'components/icon.html' import Icon %}
|
{% from 'components/icon.html' import Icon %}
|
||||||
|
|
||||||
{% macro StickyCTA(text, context=None, return_link_url=None, return_link_text=None) -%}
|
{% macro StickyCTA(text, context=None) -%}
|
||||||
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
|
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
|
||||||
{% if return_link_url and return_link_text %}
|
|
||||||
<div class="sticky-cta-return-link">
|
|
||||||
<a href="{{ return_link_url }}">
|
|
||||||
{{ Icon('caret_left', classes="icon--tiny icon--blue") }} {{ return_link_text}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="sticky-cta-container">
|
<div class="sticky-cta-container">
|
||||||
<div class="sticky-cta-text">
|
<div class="sticky-cta-text">
|
||||||
<h3>{{ text }}</h3>
|
<h3>{{ text }}</h3>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
classes='',
|
classes='',
|
||||||
noMaxWidth=False,
|
noMaxWidth=False,
|
||||||
optional=True,
|
optional=True,
|
||||||
|
showOptional=True,
|
||||||
showLabel=True,
|
showLabel=True,
|
||||||
watch=False,
|
watch=False,
|
||||||
show_validation=True) -%}
|
show_validation=True) -%}
|
||||||
@ -40,7 +41,7 @@
|
|||||||
<label for={{field.name}}>
|
<label for={{field.name}}>
|
||||||
<div class="usa-input__title">
|
<div class="usa-input__title">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
{% if optional %}
|
{% if optional and showOptional %}
|
||||||
<span class="usa-input-label-helper">(optional)</span>
|
<span class="usa-input-label-helper">(optional)</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if tooltip and not disabled %}
|
{% if tooltip and not disabled %}
|
||||||
@ -108,12 +109,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{% if show_validation %}
|
{% if show_validation %}
|
||||||
<template v-if='showError'>
|
<span v-if='showError' class='usa-input__message' v-html='validationError'></span>
|
||||||
<span class='usa-input__message' v-html='validationError'></span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class='usa-input__message'></span>
|
|
||||||
</template>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
) %}
|
) %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="subheading" id="application-members">
|
||||||
|
{{ 'portfolios.applications.settings.team_members' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if g.matchesPath("application-members") %}
|
{% if g.matchesPath("application-members") %}
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="subheading">
|
|
||||||
{{ 'portfolios.applications.settings.team_members' | translate }}
|
|
||||||
</div>
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
{% if not application.members %}
|
{% if not application.members %}
|
||||||
<div class='empty-state panel__content'>
|
<div class='empty-state panel__content'>
|
||||||
@ -87,7 +90,7 @@
|
|||||||
{{ member.update_invite_form.csrf_token }}
|
{{ member.update_invite_form.csrf_token }}
|
||||||
{{ member_fields.InfoFields(member.update_invite_form) }}
|
{{ member_fields.InfoFields(member.update_invite_form) }}
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
{{ SaveButton(text='Resend Invite', element='input', additional_classes='action-group__action') }}
|
<input type="submit" class="usa-button usa-button-primary action-group__action" tabindex="0" value="Resend Invite" />
|
||||||
<a class='action-group__action' v-on:click="closeModal('{{ resend_invite_modal }}')">{{ "common.cancel" | translate }}</a>
|
<a class='action-group__action' v-on:click="closeModal('{{ resend_invite_modal }}')">{{ "common.cancel" | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -113,7 +116,7 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<section class="member-list application-list" id="application-members">
|
<section class="member-list application-list">
|
||||||
<div class='responsive-table-wrapper'>
|
<div class='responsive-table-wrapper'>
|
||||||
<table class="atat-table">
|
<table class="atat-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -13,11 +13,19 @@
|
|||||||
<div class='portfolio-header row'>
|
<div class='portfolio-header row'>
|
||||||
<div class='col col--grow'>
|
<div class='col col--grow'>
|
||||||
<div class='portfolio-header__name'>
|
<div class='portfolio-header__name'>
|
||||||
|
<p>{{ "portfolios.header" | translate }}</p>
|
||||||
<h1>{{ portfolio.name }}</h1>
|
<h1>{{ portfolio.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='row links'>
|
<div class='row links'>
|
||||||
{% if user_can(permissions.VIEW_PORTFOLIO_FUNDING) %}
|
{% if user_can(permissions.VIEW_PORTFOLIO_ADMIN) %}
|
||||||
|
{{ Link(
|
||||||
|
icon='cog',
|
||||||
|
text='navigation.portfolio_navigation.breadcrumbs.admin' | translate,
|
||||||
|
url=url_for("portfolios.admin", portfolio_id=portfolio.id),
|
||||||
|
active=request.url_rule.endpoint == "portfolios.admin",
|
||||||
|
) }}
|
||||||
|
{% endif %}{% if user_can(permissions.VIEW_PORTFOLIO_FUNDING) %}
|
||||||
{{ Link(
|
{{ Link(
|
||||||
icon='funding',
|
icon='funding',
|
||||||
text='navigation.portfolio_navigation.breadcrumbs.funding' | translate,
|
text='navigation.portfolio_navigation.breadcrumbs.funding' | translate,
|
||||||
@ -39,13 +47,6 @@
|
|||||||
active=request.url_rule.endpoint == "portfolios.reports",
|
active=request.url_rule.endpoint == "portfolios.reports",
|
||||||
) }}
|
) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user_can(permissions.VIEW_PORTFOLIO_ADMIN) %}
|
|
||||||
{{ Link(
|
|
||||||
icon='cog',
|
|
||||||
text='navigation.portfolio_navigation.breadcrumbs.admin' | translate,
|
|
||||||
url=url_for("portfolios.admin", portfolio_id=portfolio.id),
|
|
||||||
active=request.url_rule.endpoint == "portfolios.admin",
|
|
||||||
) }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
BROWSERSTACK_CONFIG = {
|
|
||||||
"win7_ie10": {
|
|
||||||
"browser": "IE",
|
|
||||||
"browser_version": "10.0",
|
|
||||||
"os": "Windows",
|
|
||||||
"os_version": "7",
|
|
||||||
"resolution": "1024x768",
|
|
||||||
"browserstack.local": True,
|
|
||||||
},
|
|
||||||
"win10_chrome62": {
|
|
||||||
"browser": "Chrome",
|
|
||||||
"browser_version": "62.0",
|
|
||||||
"os": "Windows",
|
|
||||||
"os_version": "10",
|
|
||||||
"resolution": "1024x768",
|
|
||||||
"browserstack.local": True,
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
import os
|
|
||||||
import pytest
|
|
||||||
import logging
|
|
||||||
from collections import Mapping
|
|
||||||
from selenium import webdriver
|
|
||||||
from selenium.webdriver.common.keys import Keys
|
|
||||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
|
||||||
|
|
||||||
from .browsers import BROWSERSTACK_CONFIG
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function", autouse=True)
|
|
||||||
def session(db, request):
|
|
||||||
"""
|
|
||||||
Override base test session
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DriverCollection(Mapping):
|
|
||||||
"""
|
|
||||||
Allows access to drivers with dictionary syntax. Keeps track of which ones
|
|
||||||
have already been initialized. Allows teardown of all existing drivers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._drivers = {}
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._drivers)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._drivers)
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
if name in self._drivers:
|
|
||||||
return self._drivers[name]
|
|
||||||
|
|
||||||
elif name in BROWSERSTACK_CONFIG:
|
|
||||||
self._drivers[name] = self._build_driver(name)
|
|
||||||
return self._drivers[name]
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise AttributeError("Driver {} not found".format(name))
|
|
||||||
|
|
||||||
def _build_driver(self, config_key):
|
|
||||||
return webdriver.Remote(
|
|
||||||
command_executor="http://{}:{}@hub.browserstack.com:80/wd/hub".format(
|
|
||||||
os.getenv("BROWSERSTACK_EMAIL"), os.getenv("BROWSERSTACK_TOKEN")
|
|
||||||
),
|
|
||||||
desired_capabilities=BROWSERSTACK_CONFIG.get(config_key),
|
|
||||||
)
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
for driver in self._drivers.values():
|
|
||||||
driver.quit()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def drivers():
|
|
||||||
driver_collection = DriverCollection()
|
|
||||||
|
|
||||||
yield driver_collection
|
|
||||||
|
|
||||||
driver_collection.teardown()
|
|
@ -1,50 +0,0 @@
|
|||||||
import pytest
|
|
||||||
import requests
|
|
||||||
from flask import url_for
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
from .browsers import BROWSERSTACK_CONFIG
|
|
||||||
from atst.domain.users import Users
|
|
||||||
import atst.domain.exceptions as exceptions
|
|
||||||
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
|
||||||
from tests.test_auth import _login
|
|
||||||
|
|
||||||
import cryptography.x509 as x509
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
|
||||||
|
|
||||||
|
|
||||||
USER_CERT = "ssl/client-certs/atat.mil.crt"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("browser_type", BROWSERSTACK_CONFIG.keys())
|
|
||||||
@pytest.mark.usefixtures("live_server")
|
|
||||||
def test_can_get_title(browser_type, app, drivers):
|
|
||||||
driver = drivers[browser_type]
|
|
||||||
driver.get(url_for("atst.root", _external=True))
|
|
||||||
assert "JEDI" in driver.title
|
|
||||||
|
|
||||||
|
|
||||||
def _get_common_name(cert_path):
|
|
||||||
with open(USER_CERT, "rb") as cert_file:
|
|
||||||
cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())
|
|
||||||
common_names = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
|
||||||
return common_names[0].value
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def valid_user_from_cert():
|
|
||||||
cn = _get_common_name(USER_CERT)
|
|
||||||
cn_parts = cn.split(".")
|
|
||||||
user_info = {
|
|
||||||
"last_name": cn_parts[0],
|
|
||||||
"first_name": cn_parts[1],
|
|
||||||
"dod_id": cn_parts[-1],
|
|
||||||
"atat_role_name": "developer",
|
|
||||||
}
|
|
||||||
return Users.get_or_create_by_dod_id(**user_info)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("live_server")
|
|
||||||
def test_login(drivers, client, app, valid_user_from_cert):
|
|
||||||
driver = drivers["win7_ie10"]
|
|
||||||
driver.get(url_for("dev.login_dev", _external=True))
|
|
||||||
assert "Sign in" not in driver.title
|
|
@ -1,3 +1,5 @@
|
|||||||
|
from atst.domain.application_roles import ApplicationRoles
|
||||||
|
from atst.models import ApplicationRoleStatus
|
||||||
from atst.models import AuditEvent
|
from atst.models import AuditEvent
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
@ -16,6 +18,22 @@ def test_application_environments_excludes_deleted():
|
|||||||
assert app.environments[0].id == env.id
|
assert app.environments[0].id == env.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_application_members_excludes_deleted(session):
|
||||||
|
app = ApplicationFactory.create()
|
||||||
|
member_role = ApplicationRoleFactory.create(
|
||||||
|
application=app, status=ApplicationRoleStatus.ACTIVE
|
||||||
|
)
|
||||||
|
disabled_role = ApplicationRoleFactory.create(
|
||||||
|
application=app, status=ApplicationRoleStatus.DISABLED
|
||||||
|
)
|
||||||
|
disabled_role.deleted = True
|
||||||
|
session.add(disabled_role)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
assert len(app.members) == 1
|
||||||
|
assert app.members[0].id == member_role.id
|
||||||
|
|
||||||
|
|
||||||
def test_audit_event_for_application_deletion(session):
|
def test_audit_event_for_application_deletion(session):
|
||||||
app = ApplicationFactory.create()
|
app = ApplicationFactory.create()
|
||||||
app.deleted = True
|
app.deleted = True
|
||||||
|
@ -10,6 +10,7 @@ from atst.domain.application_roles import ApplicationRoles
|
|||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.domain.common import Paginator
|
from atst.domain.common import Paginator
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
|
from atst.models.application_role import Status as ApplicationRoleStatus
|
||||||
from atst.models.environment_role import CSPRole
|
from atst.models.environment_role import CSPRole
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.forms.application import EditEnvironmentForm
|
from atst.forms.application import EditEnvironmentForm
|
||||||
@ -560,6 +561,8 @@ def test_revoke_invite(client, user_session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert invite.is_revoked
|
assert invite.is_revoked
|
||||||
|
assert app_role.status == ApplicationRoleStatus.DISABLED
|
||||||
|
assert app_role.deleted
|
||||||
|
|
||||||
|
|
||||||
def test_filter_environment_roles():
|
def test_filter_environment_roles():
|
||||||
|
@ -107,7 +107,9 @@ email:
|
|||||||
environment_ready: JEDI cloud environment ready
|
environment_ready: JEDI cloud environment ready
|
||||||
flash:
|
flash:
|
||||||
application:
|
application:
|
||||||
created: 'You have successfully created the {application_name} application.'
|
created:
|
||||||
|
title: Application Saved
|
||||||
|
message: '{application_name} has been successfully created. You may continue on to provision environments and assign team members now, or come back and complete these tasks at a later time.'
|
||||||
updated: 'You have successfully updated the {application_name} application.'
|
updated: 'You have successfully updated the {application_name} application.'
|
||||||
deleted: 'You have successfully deleted the {application_name} application. To view the retained activity log, visit the portfolio administration page.'
|
deleted: 'You have successfully deleted the {application_name} application. To view the retained activity log, visit the portfolio administration page.'
|
||||||
delete_member_success: 'You have successfully deleted {member_name} from the portfolio.'
|
delete_member_success: 'You have successfully deleted {member_name} from the portfolio.'
|
||||||
@ -119,7 +121,9 @@ flash:
|
|||||||
new_ppoc_message: 'You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC.'
|
new_ppoc_message: 'You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC.'
|
||||||
new_ppoc_title: Primary point of contact updated
|
new_ppoc_title: Primary point of contact updated
|
||||||
success: Success!
|
success: Success!
|
||||||
new_application_member: '{user_name} has been added to {application_name}. They will not have access until they accept the invitation e-mailed to them and CSP processing is complete.'
|
new_application_member:
|
||||||
|
title: "{user_name}'s invitation has been sent"
|
||||||
|
message: "{user_name}'s access to this Application is pending until they sign in for the first time."
|
||||||
updated_application_team_settings: 'You have updated the {application_name} team settings.'
|
updated_application_team_settings: 'You have updated the {application_name} team settings.'
|
||||||
logged_out: Logged out
|
logged_out: Logged out
|
||||||
footer:
|
footer:
|
||||||
@ -127,11 +131,11 @@ footer:
|
|||||||
login: 'Last login:'
|
login: 'Last login:'
|
||||||
forms:
|
forms:
|
||||||
application:
|
application:
|
||||||
description_label: Description
|
description_label: Application Description
|
||||||
environment_names_label: Environment Name
|
environment_names_label: Environment Name
|
||||||
environment_names_required_validation_message: Provide at least one environment name.
|
environment_names_required_validation_message: Provide at least one environment name.
|
||||||
environment_names_unique_validation_message: Environment names must be unique.
|
environment_names_unique_validation_message: Environment names must be unique.
|
||||||
name_label: Name
|
name_label: Application Name
|
||||||
assign_ppoc:
|
assign_ppoc:
|
||||||
dod_id: 'Select new primary point of contact:'
|
dod_id: 'Select new primary point of contact:'
|
||||||
environments:
|
environments:
|
||||||
@ -283,8 +287,8 @@ login:
|
|||||||
navigation:
|
navigation:
|
||||||
portfolio_navigation:
|
portfolio_navigation:
|
||||||
breadcrumbs:
|
breadcrumbs:
|
||||||
admin: Admin
|
admin: Settings
|
||||||
funding: Funding
|
funding: Task Orders
|
||||||
reports: Reports
|
reports: Reports
|
||||||
applications: Applications
|
applications: Applications
|
||||||
topbar:
|
topbar:
|
||||||
@ -311,12 +315,12 @@ portfolios:
|
|||||||
app_settings_text: App settings
|
app_settings_text: App settings
|
||||||
new:
|
new:
|
||||||
step_1_header: Name and Describe New Application
|
step_1_header: Name and Describe New Application
|
||||||
step_1_button_text: "Save and Add Environments"
|
step_1_button_text: "Next: Add Environments"
|
||||||
step_1_form_help_text:
|
step_1_form_help_text:
|
||||||
name: |
|
name: |
|
||||||
<div style="margin-top: -3rem;">
|
<div>
|
||||||
<p>
|
<p>
|
||||||
The name of your application should be intuitive and easily recognizable for all of your team members.
|
The name of your Application should be intuitive and easily recognizable for all of your team members.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Writer's Block? A naming example includes:</strong>
|
<strong>Writer's Block? A naming example includes:</strong>
|
||||||
@ -326,12 +330,12 @@ portfolios:
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
description: |
|
description: |
|
||||||
<div style="margin-top: -3rem;">
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Add a brief one to two sentence description of your application. You should be able to reference your TO Description of Work.
|
Add a brief one to two sentence description of your application. You should be able to reference your TO Description of Work.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Writer's Block? A naming example includes:</strong>
|
<strong>Writer's Block? A description example includes:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Build security applications for FOB Clark</li>
|
<li>Build security applications for FOB Clark</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -339,9 +343,9 @@ portfolios:
|
|||||||
</div>
|
</div>
|
||||||
step_2_header: Add Environments to {application_name}
|
step_2_header: Add Environments to {application_name}
|
||||||
step_2_description: "Production, Staging, Testing, and Development environments are included by default. However, you can add, edit, and delete environments based on the needs of your Application."
|
step_2_description: "Production, Staging, Testing, and Development environments are included by default. However, you can add, edit, and delete environments based on the needs of your Application."
|
||||||
step_2_button_text: "Save and Add Members"
|
step_2_button_text: "Next: Add Members"
|
||||||
step_3_header: Invite Members to {application_name}
|
step_3_header: Add Members to {application_name}
|
||||||
step_3_description: "To proceed, you will need each member's email address and DOD ID. Within this section, you will also assign application-level permissions and environment-level roles for each member."
|
step_3_description: "To proceed, you will need each member's email address and DOD ID. Within this section, you will also assign Application-level permissions and environment-level roles for each member."
|
||||||
step_3_button_text: Save Application
|
step_3_button_text: Save Application
|
||||||
create_new_env: Create a new environment.
|
create_new_env: Create a new environment.
|
||||||
create_new_env_info: Creating an environment gives you access to the Cloud Service Provider. This environment will function within the constraints of the task order, and any costs will be billed against the portfolio.
|
create_new_env_info: Creating an environment gives you access to the Cloud Service Provider. This environment will function within the constraints of the task order, and any costs will be billed against the portfolio.
|
||||||
@ -371,7 +375,7 @@ portfolios:
|
|||||||
new_application_title: New Application
|
new_application_title: New Application
|
||||||
settings_heading: Application Settings
|
settings_heading: Application Settings
|
||||||
settings:
|
settings:
|
||||||
name_description: Name and Description
|
name_description: Application name and description
|
||||||
team_members: Team Members
|
team_members: Team Members
|
||||||
team_settings:
|
team_settings:
|
||||||
blank_slate:
|
blank_slate:
|
||||||
@ -424,6 +428,7 @@ portfolios:
|
|||||||
empty:
|
empty:
|
||||||
start_button: Start a new JEDI portfolio
|
start_button: Start a new JEDI portfolio
|
||||||
title: You have no apps yet
|
title: You have no apps yet
|
||||||
|
header: PORTFOLIO
|
||||||
members:
|
members:
|
||||||
archive_button: Delete member
|
archive_button: Delete member
|
||||||
permissions:
|
permissions:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user