Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Bobel
cc6e1c5265 Updating content on Dashboard 2023-10-24 14:34:45 -04:00
801 changed files with 37169 additions and 81363 deletions

View File

@@ -1,638 +0,0 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
},
{
"name": "AWSKeyDetector"
},
{
"name": "AzureStorageKeyDetector"
},
{
"name": "Base64HighEntropyString",
"limit": 4.5
},
{
"name": "BasicAuthDetector"
},
{
"name": "CloudantDetector"
},
{
"name": "DiscordBotTokenDetector"
},
{
"name": "GitHubTokenDetector"
},
{
"name": "GitLabTokenDetector"
},
{
"name": "HexHighEntropyString",
"limit": 3.0
},
{
"name": "IbmCloudIamDetector"
},
{
"name": "IbmCosHmacDetector"
},
{
"name": "IPPublicDetector"
},
{
"name": "JwtTokenDetector"
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
},
{
"name": "MailchimpDetector"
},
{
"name": "NpmDetector"
},
{
"name": "OpenAIDetector"
},
{
"name": "PrivateKeyDetector"
},
{
"name": "PypiTokenDetector"
},
{
"name": "SendGridDetector"
},
{
"name": "SlackDetector"
},
{
"name": "SoftlayerDetector"
},
{
"name": "SquareOAuthDetector"
},
{
"name": "StripeDetector"
},
{
"name": "TelegramBotTokenDetector"
},
{
"name": "TwilioKeyDetector"
}
],
"filters_used": [
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_baseline_file",
"filename": ".ds.baseline"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
{
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
},
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
}
],
"results": {
".github/workflows/checks.yml": [
{
"type": "Secret Keyword",
"filename": ".github/workflows/checks.yml",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 68,
"is_secret": false
}
],
"app/assets/js/uswds.min.js": [
{
"type": "Secret Keyword",
"filename": "app/assets/js/uswds.min.js",
"hashed_secret": "372ea08cab33e71c02c651dbc83a474d32c676ea",
"is_verified": false,
"line_number": 85,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "app/assets/js/uswds.min.js",
"hashed_secret": "53e07a32bf191d6917ee6fd863f0b52632a86798",
"is_verified": false,
"line_number": 85,
"is_secret": false
}
],
"app/config.py": [
{
"type": "Secret Keyword",
"filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false,
"line_number": 121,
"is_secret": false
}
],
"app/main/_commonly_used_passwords.py": [
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "82e19fa12aab7cfc718a002fc82c0f074bf070e7",
"is_verified": false,
"line_number": 123,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "a172ffc990129fe6f68b50f6037c54a1894ee3fd",
"is_verified": false,
"line_number": 240,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "4de69ee6b12b7fc91070873b71ba6e2929b90619",
"is_verified": false,
"line_number": 244,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "370194ff6e0f93a7432e16cc9badd9427e8b4e13",
"is_verified": false,
"line_number": 284,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "3dd635a808ddb6dd4b6731f7c409d53dd4b14df2",
"is_verified": false,
"line_number": 356,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "67a74306b06d0c01624fe0d0249a570f4d093747",
"is_verified": false,
"line_number": 374,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "61d6504733ca7757e259c644acd085c4dd471019",
"is_verified": false,
"line_number": 910,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "4ea872dfd7eefbde0036da7f0780826353dc7477",
"is_verified": false,
"line_number": 940,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "b214f706bb602c1cc2adc5c6165e73622305f4bb",
"is_verified": false,
"line_number": 1010,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "5cbabd43e49a1fedbbc3b86311aa6c8fe446abf9",
"is_verified": false,
"line_number": 1195,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "18ad10fd4a67f21fc07b1aa5046b410f6b2bedf1",
"is_verified": false,
"line_number": 1213,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "10470c3b4b1fed12c3baac014be15fac67c6e815",
"is_verified": false,
"line_number": 1263,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "65e1946c8f102eca8ba0af291f7c5e807516d94c",
"is_verified": false,
"line_number": 1346,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "0075df0a74c07ee295c98238c018401c9a80183b",
"is_verified": false,
"line_number": 1397,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "ca0023d7b345802fbc227b902cb9c57a3e02195f",
"is_verified": false,
"line_number": 1442,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "c8c6ca2e11c2dfd2a40914585b5944bffea15c8c",
"is_verified": false,
"line_number": 1555,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "b85b97a99eab8c809570c61d6404c1e49bdefbb4",
"is_verified": false,
"line_number": 1596,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "dec7dd342a499dfd4d283d872ccf598d8a7b6039",
"is_verified": false,
"line_number": 1789,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "2dc5053699a351121bf839c446bd4a878dda5735",
"is_verified": false,
"line_number": 1939,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "e5d54f0ac13abbdaa94b696c2469148b96dd11ab",
"is_verified": false,
"line_number": 2242,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "6059f42e2bbae78141e8a9e6286755ee691d5ce0",
"is_verified": false,
"line_number": 2305,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "fe703d258c7ef5f50b71e06565a65aa07194907f",
"is_verified": false,
"line_number": 2348,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "c229b68e1c3ffd9874838b5cb5354a0ee1367ddc",
"is_verified": false,
"line_number": 2349,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "756de479126e911b6f3400ae686d663d9d26b509",
"is_verified": false,
"line_number": 2920,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "6b174322afcdb440ee9cc3cc11eb16f9a00dec04",
"is_verified": false,
"line_number": 2975,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "9860783bfb510cbb2bf34471ec0b84a7ea587695",
"is_verified": false,
"line_number": 3359,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "b227cbd22eaa96019ebfc4aff35ad2add2a47439",
"is_verified": false,
"line_number": 3590,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "381d48209aecab8834eb495c5b5406100da07882",
"is_verified": false,
"line_number": 3811,
"is_secret": false
},
{
"type": "Hex High Entropy String",
"filename": "app/main/_commonly_used_passwords.py",
"hashed_secret": "508b38590a90d32990aadd7350d160b795c3ab41",
"is_verified": false,
"line_number": 3850,
"is_secret": false
}
],
"deploy-config/sandbox.yml": [
{
"type": "Secret Keyword",
"filename": "deploy-config/sandbox.yml",
"hashed_secret": "113151dd10316fcb0d5507b6215d78e2f3fe9e54",
"is_verified": false,
"line_number": 9,
"is_secret": false
}
],
"pytest.ini": [
{
"type": "Secret Keyword",
"filename": "pytest.ini",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false,
"line_number": 7,
"is_secret": false
},
{
"type": "Base64 High Entropy String",
"filename": "pytest.ini",
"hashed_secret": "d347784b1ab6074a65cda7bc42f1561bed85493f",
"is_verified": false,
"line_number": 7,
"is_secret": false
},
{
"type": "Base64 High Entropy String",
"filename": "pytest.ini",
"hashed_secret": "ed1754d5cc82c8fd83205ebfb8c43fe4e88415a4",
"is_verified": false,
"line_number": 9,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "pytest.ini",
"hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
"is_verified": false,
"line_number": 11,
"is_secret": false
}
],
"tests/__init__.py": [
{
"type": "Secret Keyword",
"filename": "tests/__init__.py",
"hashed_secret": "f8377c90fcfd699f0ddbdcb30c2c9183d2d933ea",
"is_verified": false,
"line_number": 389,
"is_secret": false
}
],
"tests/app/main/forms/test_register_user_form.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/forms/test_register_user_form.py",
"hashed_secret": "8c6c978dc8e08771c7dea1ea2370fdf2446e5ba5",
"is_verified": false,
"line_number": 38,
"is_secret": false
}
],
"tests/app/main/test_errorhandlers.py": [
{
"type": "Base64 High Entropy String",
"filename": "tests/app/main/test_errorhandlers.py",
"hashed_secret": "005fa73b3f2be8f0d71d361c1f0a9d787cd09b4e",
"is_verified": false,
"line_number": 34,
"is_secret": false
}
],
"tests/app/main/test_request_header.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/test_request_header.py",
"hashed_secret": "6866ef97a972ba3a2c6ff8bb2812981054770162",
"is_verified": false,
"line_number": 21,
"is_secret": false
}
],
"tests/app/main/views/organizations/test_organization_invites.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/organizations/test_organization_invites.py",
"hashed_secret": "bdbb156d25d02fd7792865824201dda1c60f4473",
"is_verified": false,
"line_number": 274,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/organizations/test_organization_invites.py",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 282,
"is_secret": false
}
],
"tests/app/main/views/test_accept_invite.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_accept_invite.py",
"hashed_secret": "07f0a6c13923fc3b5f0c57ffa2d29b715eb80d71",
"is_verified": false,
"line_number": 632,
"is_secret": false
}
],
"tests/app/main/views/test_new_password.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_new_password.py",
"hashed_secret": "a41d5c3bbcd0b39c627b9cbf4897c6d25efa694f",
"is_verified": false,
"line_number": 89,
"is_secret": false
}
],
"tests/app/main/views/test_register.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_register.py",
"hashed_secret": "bdbb156d25d02fd7792865824201dda1c60f4473",
"is_verified": false,
"line_number": 115,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_register.py",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 185,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_register.py",
"hashed_secret": "bb5b7caa27d005d38039e3797c3ddb9bcd22c3c8",
"is_verified": false,
"line_number": 256,
"is_secret": false
}
],
"tests/app/main/views/test_sign_in.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_sign_in.py",
"hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9",
"is_verified": false,
"line_number": 95,
"is_secret": false
}
],
"tests/app/main/views/test_two_factor.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_two_factor.py",
"hashed_secret": "dc66ad927c29e31c6c374231f57a4684b0687bfe",
"is_verified": false,
"line_number": 290,
"is_secret": false
}
],
"tests/app/main/views/test_user_profile.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_user_profile.py",
"hashed_secret": "8072d7aad32964ec43fbcb699c75dc38890792f7",
"is_verified": false,
"line_number": 336,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_user_profile.py",
"hashed_secret": "4c9dbb972da179e4f66f023eaa5fb9451d835030",
"is_verified": false,
"line_number": 337,
"is_secret": false
}
],
"tests/app/main/views/test_verify.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/main/views/test_verify.py",
"hashed_secret": "faafcfa63e128929409bf310b7ea5a415f2331ce",
"is_verified": false,
"line_number": 160,
"is_secret": false
}
],
"tests/app/notify_client/test_user_client.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/notify_client/test_user_client.py",
"hashed_secret": "f2c57870308dc87f432e5912d4de6f8e322721ba",
"is_verified": false,
"line_number": 56,
"is_secret": false
}
],
"tests/app/test_cloudfoundry_config.py": [
{
"type": "Secret Keyword",
"filename": "tests/app/test_cloudfoundry_config.py",
"hashed_secret": "5e44dae2de8b6e57c797b968035265c9f2cd2b3e",
"is_verified": false,
"line_number": 12,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/test_cloudfoundry_config.py",
"hashed_secret": "e5e178db7317356946d13e5d2da037d39ac61c71",
"is_verified": false,
"line_number": 27,
"is_secret": false
}
],
"tests/conftest.py": [
{
"type": "Secret Keyword",
"filename": "tests/conftest.py",
"hashed_secret": "f8377c90fcfd699f0ddbdcb30c2c9183d2d933ea",
"is_verified": false,
"line_number": 3267,
"is_secret": false
}
]
},
"generated_at": "2025-08-01T16:55:36Z"
}

View File

@@ -1,71 +0,0 @@
name: "Bug Report"
description: "Use this template to file a bug report"
title: "Bug: [Brief description of the bug]"
labels:
- "Type: Bug"
body:
- type: markdown
attributes:
value: |
Please use this form to file new bugs! Add screenshots to the comments of this initial form.
- type: input
id: contact
attributes:
label: Who discovered this?
description: Was it something you found or are you filing for someone else? If someone else, how can we contact them?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: environment
attributes:
label: Environment
description: In which environment did you experience the bug?
options:
- staging
- demo
- production
validations:
required: true
- type: textarea
id: page
attributes:
label: What pages did this appear on?
description: please include URLs of where you saw the bug
validations:
required: true
- type: textarea
id: reproSteps
attributes:
label: "Detail the steps for someone to reproduce"
description: "Reproduction steps"
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View File

@@ -1 +0,0 @@
blank_issues_enabled: true

View File

@@ -1,87 +0,0 @@
name: "User Story Template"
description: "Use this template for creating user stories"
title: "User Story: [Brief description of the user story]"
labels:
- "Type: User Story"
body:
- type: markdown
attributes:
value: '**User Story:**'
- type: textarea
id: userType
attributes:
label: "As a [type of user],"
description: "Describe the type of user involved in this story."
validations:
required: true
- type: textarea
id: actionFeature
attributes:
label: "I want [an action or feature],"
description: "Describe the desired action or feature from the user's perspective."
validations:
required: true
- type: textarea
id: benefitValue
attributes:
label: "So that [benefit or value]."
description: "Describe the benefit or value the user expects from the action or feature."
validations:
required: true
- type: markdown
attributes:
value: '**Acceptance Criteria:**'
- type: textarea
id: acceptanceCriteria
attributes:
label: "Detailed condition or criteria that must be met for the user story to be considered complete."
description: "List the acceptance criteria for the user story."
validations:
required: true
- type: markdown
attributes:
value: '**Tasks:**'
- type: textarea
id: tasks
attributes:
label: "List of specific tasks or sub-tasks that need to be done to implement the user story."
description: "Outline the tasks necessary to implement the user story."
validations:
required: true
- type: markdown
attributes:
value: '**Dependencies:**'
- type: textarea
id: dependencies
attributes:
label: "List any dependencies that need to be resolved before starting or completing this user story."
description: "Specify any dependencies related to the user story."
validations:
required: false
- type: markdown
attributes:
value: '**Accessibility:**'
- type: textarea
id: accessibility
attributes:
label: "List any specific accessibility guidance or tests that need to be considered for this user story."
description: "List what type of accessibility tests need to pass."
validations:
required: false
- type: markdown
attributes:
value: '**Notes:**'
- type: textarea
id: notes
attributes:
label: "Any additional information or context that might be useful for the team."
description: "Provide any extra notes or context for the user story."
validations:
required: false

View File

@@ -1,80 +0,0 @@
name: "QA Checklist"
description: "Use this template to manually QA for prod push"
title: "QA for prod push: Admin #[insert PR number], API #[insert PR number], Utils #[insert PR number]"
body:
- type: checkboxes
id: Logging_in
attributes:
label: Log In
options:
- label: notify-staging.app.cloud.gov resolves
- label: Clicking sign in takes you to Login.gov
- label: Can sign in via Login.gov
- label: After successful login, you are brought to the “choose service” screen
- label: Choosing a service brings you to the dashboard for that service
- type: checkboxes
id: send_template
attributes:
label: Create a template and send
options:
- label: Clicking “Send messages” brings you to templates page
- label: When “Create a template” button is clicked, the option to create new or copy existing template appears
- label: Uploading a csv is quick and automatically directs you to the preview/send page
- label: Formatting looks clean and in place
- label: Message dashboard populates and shows “pending” until call is made to AWS for delivery receipt
- type: checkboxes
id: send__copied_template
attributes:
label: Send a message from copied template
options:
- label: When “Create a template” button is clicked, the option to create new or copy existing template appears
- label: Clicking “copy an existing template” allows you to choose from pre-existing template
- label: After editing, the new template saves and you prepare to send
- type: checkboxes
id: send__existing_template
attributes:
label: Send a message from existing template
options:
- label: Clicking on existing template brings you to preview screen with `prepare to send` button
- label: All else above persists as true (formatting, send, dashboard, etc)
- type: checkboxes
id: download_report
attributes:
label: Download the csv delivery report
options:
- label: Clicking on the csv download directly after a message send downloads a report that contains the data we expect
- label: The batch report appears on the main dashboard batch table with accurate information
- label: Clicking on the csv download link from the main dashboard downloads a report that contains the data we expect
- type: checkboxes
id: invite_member
attributes:
label: Invite a team member
options:
- label: Clicking on “Team” in settings brings you to team member screen
- label: Clicking “Invite a team member” brings you to email and permissions screen
- label: Clicking invite team member sends an invite and shows confirmation
- label: Invite email provides live link that works to register new team member
- type: checkboxes
id: delete_member
attributes:
label: Delete a team member
options:
- label: From team member screen, clicking “Change Details” on a specific profile brings you to that persons profile
- label: Clicking on “Remove this team member” prompts a confirmation
- label: Clicking the confirmation removes the team member
- type: checkboxes
id: sign_out
attributes:
label: Sign Out
options:
- label: Clicking “Sign Out” takes you back to the homepage and logs you out

View File

@@ -1,9 +1,6 @@
name: Deploy egress proxy
description: Set egress space security groups and deploy proxy
inputs:
cf_org:
description: The org the target app exists in.
required: true
cf_space:
description: The space the target app exists in.
required: true
@@ -19,19 +16,6 @@ inputs:
runs:
using: composite
steps:
- name: Install cf-cli
shell: bash
run: |
curl -A "cg-deploy-action" -v -L -o cf-cli_amd64.deb 'https://packages.cloudfoundry.org/stable?release=debian64&version=v8&source=github'
sudo dpkg -i cf-cli_amd64.deb
- name: Login to cf-cli
shell: bash
run: |
cf api api.fr.cloud.gov
cf auth
- name: Target org and space
shell: bash
run: cf target -o ${{ inputs.cf_org }} -s ${{ inputs.cf_space }}
- name: Set restricted space egress
shell: bash
run: ./terraform/set_space_egress.sh -t -s ${{ inputs.cf_space }}

View File

@@ -9,19 +9,13 @@ runs:
sudo apt-get update \
&& sudo apt-get install -y --no-install-recommends \
libcurl4-openssl-dev
- name: Set up Python 3.12.9
uses: actions/setup-python@v4
- name: Set up Python 3.9
uses: actions/setup-python@v3
with:
python-version: "3.12.9"
python-version: "3.9"
- name: Install poetry
shell: bash
run: pip install poetry==2.1.3
- name: Install poetry export plugin
shell: bash
run: poetry self add poetry-plugin-export
- name: Downgrade virtualenv to compatible version
shell: bash
run: pip install "virtualenv<20.30"
run: pip install poetry
- name: Install application dependencies
shell: bash
run: make bootstrap

View File

@@ -3,18 +3,14 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
version: 3
updates:
- package-ecosystem: 'pip' # See documentation for possible values
directory: '/' # Location of package manifests
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: 'daily'
assignees:
- 'alexjanousekGSA'
interval: "daily"
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
versioning-strategy: increase
assignees:
- 'alexjanousekGSA'

View File

@@ -1,29 +0,0 @@
<!--
Not sure what you should include or write in a pull request? Please read the
[pull request documentation in our docs!](https://github.com/GSA/notifications-api/blob/main/docs/all.md#pull-requests)
-->
*A note to PR reviewers: it may be helpful to review our [code review documentation](https://github.com/GSA/notifications-api/blob/main/docs/all.md#code-reviews) to know what to keep in mind while reviewing pull requests.*
## Description
Please enter a detailed description here.
## TODO (optional)
* [ ] TODO item 1
* [ ] TODO item 2
* [ ] TODO item ...
## Security Considerations
* Consideration 1
* Consideration 2
* Consideration ...
## A11y Checks (if applicable)
* Double check work is getting picked up by the automated E2E tests
* Conduct browser-based tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/)
* Review the [Manual Checklist](https://docs.google.com/document/d/192bBXStebdXWtYhZQ73qaWMJhGcuSB1W6c9YBXhWZvc/edit?usp=sharing)
* Make sure there are no linting errors in VSCode or other IDE of choice

View File

@@ -7,12 +7,12 @@ permissions:
env:
NOTIFY_ENVIRONMENT: test
NEW_RELIC_CONFIG_FILE: newrelic.ini
NEW_RELIC_ENVIRONMENT: test
FLASK_APP: application.py
WERKZEUG_DEBUG_PIN: off
REDIS_ENABLED: 0
NODE_VERSION: 22.3.0
AWS_US_TOLL_FREE_NUMBER: "+18556438890"
ADMIN_BASE_URL: http://localhost:6012
NODE_VERSION: 16.15.1
jobs:
build:
@@ -22,13 +22,7 @@ jobs:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22.3.0"
- name: Install dependencies
run: npm install
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- uses: jwalton/gh-find-current-pr@v1
id: findPr
@@ -38,96 +32,50 @@ jobs:
output: report-markdown
annotations: failed-tests
prnumber: ${{ steps.findPr.outputs.number }}
- name: Check imports alphabetized
run: poetry run isort --check-only ./app ./tests
- name: Run style checks
run: poetry run flake8 .
- name: Check imports alphabetized
run: poetry run isort --check-only ./app ./tests
- name: Check dead code
run: make dead-code
- name: Run js lint
run: npm run lint
- name: Run js tests
run: npm test
- name: Run py tests with coverage
run: poetry run coverage run --omit=*/notifications_utils/* -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
# - name: Run E2E tests
# run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
# env:
# NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
# NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
# NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
# NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }}
- name: Check coverage threshold
run: poetry run coverage report --fail-under=90
end-to-end-tests:
if: ${{ github.actor != 'dependabot[bot]' }}
permissions:
checks: write
pull-requests: write
contents: write
runs-on: ubuntu-latest
environment: staging
services:
postgres:
image: postgres
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: test_notification_api
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 6379 on service container to the host
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- name: Check API Server availability
run: |
curl --fail -v https://notify-api-staging.app.cloud.gov || exit 1
- name: Run Admin server
# If we want to log stuff and see what's broken,
# insert this line:
# tail -f admin-server.log &
# above make e2e-test
run: |
make run-flask > admin-server.log 2>&1 &
tail -f admin-server.log &
make e2e-test
env:
API_HOST_NAME: https://notify-api-staging.app.cloud.gov/
SECRET_KEY: ${{ secrets.SECRET_KEY }}
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
ADMIN_CLIENT_USERNAME: notify-admin
NOTIFY_ENVIRONMENT: e2etest
NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
NOTIFY_E2E_TEST_URI: http://localhost:6012/
VCAP_SERVICES: ${{ secrets.VCAP_SERVICES }}
# - name: Health check
# run: |
# response=$(curl -url ${{secrets.NOTIFY_E2E_TEST_URI}}_status)
# if grep -q "ok" <<< "$response"; then
# echo "Health check passed"
# else
# echo "Health check failed"
# exit 1
# fi
# env:
# NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
# NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
# NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
# NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }}
validate-new-relic-config:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Validate NewRelic config
env:
NEW_RELIC_CONFIG_FILE: newrelic.ini
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
# Need to set a NEW_RELIC_ENVIRONMENT with monitor_mode: true
NEW_RELIC_ENVIRONMENT: staging
@@ -136,22 +84,20 @@ jobs:
dependency-audits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --output requirements.txt
- uses: pypa/gh-action-pip-audit@v1.1.0
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- uses: pypa/gh-action-pip-audit@v1.0.6
with:
inputs: requirements.txt
ignore-vulns: |
PYSEC-2023-312
- name: Run npm audit
run: make npm-audit
static-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run scan
run: poetry run bandit -r app/ --confidence-level medium
@@ -159,27 +105,26 @@ jobs:
dynamic-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &
env:
NOTIFY_ENVIRONMENT: scanning
FEATURE_SOCKET_ENABLED: true
- name: Run OWASP Baseline Scan
uses: zaproxy/action-baseline@v0.14.0
uses: zaproxy/action-baseline@v0.9.0
with:
docker_name: "ghcr.io/zaproxy/zaproxy:weekly"
target: "http://localhost:6012"
docker_name: 'ghcr.io/zaproxy/zaproxy:weekly'
target: 'http://localhost:6012'
fail_action: true
allow_issue_writing: false
rules_file_name: "zap.conf"
cmd_options: "-I"
rules_file_name: 'zap.conf'
cmd_options: '-I'
a11y-scan:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &

View File

@@ -1,95 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "production" ]
pull_request:
branches: [ "main", "production" ]
schedule:
- cron: '18 5 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -16,28 +16,26 @@ env:
FLASK_APP: application.py
WERKZEUG_DEBUG_PIN: off
REDIS_ENABLED: 0
NODE_VERSION: 22.3.0
NODE_VERSION: 16.15.1
jobs:
dependency-audits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --output requirements.txt
- uses: pypa/gh-action-pip-audit@v1.1.0
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- uses: pypa/gh-action-pip-audit@v1.0.6
with:
inputs: requirements.txt
ignore-vulns: |
PYSEC-2023-312
- name: Run npm audit
run: make npm-audit
static-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run scan
run: poetry run bandit -r app/ --confidence-level medium
@@ -45,14 +43,14 @@ jobs:
dynamic-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &
env:
NOTIFY_ENVIRONMENT: scanning
- name: Run OWASP Full Scan
uses: zaproxy/action-full-scan@v0.12.0
uses: zaproxy/action-full-scan@v0.7.0
with:
docker_name: 'ghcr.io/zaproxy/zaproxy:weekly'
target: 'http://localhost:6012'

View File

@@ -12,25 +12,27 @@ jobs:
runs-on: ubuntu-latest
environment: demo
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v1.1.2
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
files: |
terraform/demo
terraform/shared
.github/workflows/deploy-demo.yml
- name: Terraform init
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/demo
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
run: terraform init
- name: Terraform apply
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/demo
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
@@ -42,63 +44,41 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --output requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- name: Deploy to cloud.gov
uses: cloud-gov/cg-cli-tools@main
uses: 18f/cg-deploy-action@main
env:
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
ADMIN_CLIENT_USERNAME: "notify-admin"
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
COMMIT_HASH: ${{ github.sha }}
LOGIN_PEM: ${{ secrets.LOGIN_PEM }}
LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov"
LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo"
LOGIN_DOT_GOV_ACCESS_TOKEN_URL: "https://secure.login.gov/api/openid_connect/token"
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-demo.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-demo.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-demo.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
API_PUBLIC_URL: ${{ secrets.API_PUBLIC_URL }}
API_PUBLIC_WS_URL: ${{ secrets.API_PUBLIC_WS_URL }}
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
cf_org: gsa-tts-benefits-studio
cf_space: notify-demo
cf_command: >-
push -f manifest.yml
push_arguments: >-
--vars-file deploy-config/demo.yml
--var DANGEROUS_SALT="$DANGEROUS_SALT"
--var SECRET_KEY="$SECRET_KEY"
--var ADMIN_CLIENT_USERNAME="notify-admin"
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
--var COMMIT_HASH="$COMMIT_HASH"
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
--var LOGIN_DOT_GOV_LOGOUT_URL="$LOGIN_DOT_GOV_LOGOUT_URL"
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
--var LOGIN_PEM="$LOGIN_PEM"
--var API_PUBLIC_URL="$API_PUBLIC_URL"
--var API_PUBLIC_WS_URL="$API_PUBLIC_WS_URL"
--strategy rolling
- name: Deploy egress proxy
uses: ./.github/actions/deploy-proxy
env:
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v34
with:
files: |
deploy-config/egress_proxy/notify-admin-demo.*.acl
.github/actions/deploy-proxy/action.yml
.github/workflows/deploy-demo.yml
- name: Deploy egress proxy
if: steps.changed-egress-config.outputs.any_changed == 'true'
uses: ./.github/actions/deploy-proxy
with:
cf_org: gsa-tts-benefits-studio
cf_space: notify-demo
app: notify-admin-demo

View File

@@ -12,25 +12,27 @@ jobs:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v1.1.2
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
files: |
terraform/production
terraform/shared
.github/workflows/deploy-prod.yml
- name: Terraform init
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/production
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
run: terraform init
- name: Terraform apply
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/production
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
@@ -42,63 +44,41 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --output requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- name: Deploy to cloud.gov
uses: cloud-gov/cg-cli-tools@main
uses: 18f/cg-deploy-action@main
env:
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
ADMIN_CLIENT_USERNAME: "notify-admin"
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
COMMIT_HASH: ${{ github.sha }}
LOGIN_PEM: ${{ secrets.LOGIN_PEM }}
LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov"
LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo"
LOGIN_DOT_GOV_ACCESS_TOKEN_URL: "https://secure.login.gov/api/openid_connect/token"
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://beta.notify.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://beta.notify.gov/sign-out"
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://beta.notify.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
API_PUBLIC_URL: ${{ secrets.API_PUBLIC_URL }}
API_PUBLIC_WS_URL: ${{ secrets.API_PUBLIC_WS_URL }}
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
cf_org: gsa-tts-benefits-studio
cf_space: notify-production
cf_command: >-
push -f manifest.yml
push_arguments: >-
--vars-file deploy-config/production.yml
--var DANGEROUS_SALT="$DANGEROUS_SALT"
--var SECRET_KEY="$SECRET_KEY"
--var ADMIN_CLIENT_USERNAME="notify-admin"
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
--var COMMIT_HASH="$COMMIT_HASH"
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
--var LOGIN_DOT_GOV_LOGOUT_URL="$LOGIN_DOT_GOV_LOGOUT_URL"
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
--var LOGIN_PEM="$LOGIN_PEM"
--var API_PUBLIC_URL="$API_PUBLIC_URL"
--var API_PUBLIC_WS_URL="$API_PUBLIC_WS_URL"
--strategy rolling
- name: Deploy egress proxy
uses: ./.github/actions/deploy-proxy
env:
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v34
with:
files: |
deploy-config/egress_proxy/notify-admin-production.*.acl
.github/actions/deploy-proxy/action.yml
.github/workflows/deploy-prod.yml
- name: Deploy egress proxy
if: steps.changed-egress-config.outputs.any_changed == 'true'
uses: ./.github/actions/deploy-proxy
with:
cf_org: gsa-tts-benefits-studio
cf_space: notify-production
app: notify-admin-production

View File

@@ -17,98 +17,76 @@ jobs:
environment: staging
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/checkout@v3
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v1.1.2
with:
files: |
terraform/staging
terraform/shared
.github/workflows/deploy.yml
- name: Terraform init
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
run: terraform init
- name: Terraform apply
if: steps.changed-terraform-files.outputs.any_changed == 'true'
working-directory: terraform/staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: terraform apply -auto-approve -input=false
- name: Terraform init
working-directory: terraform/staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
run: terraform init
- name: Terraform apply
working-directory: terraform/staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: terraform apply -auto-approve -input=false
- uses: ./.github/actions/setup-project
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- name: Create requirements.txt
run: poetry export --output requirements.txt
- name: Deploy to cloud.gov
uses: 18f/cg-deploy-action@main
env:
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
cf_org: gsa-tts-benefits-studio
cf_space: notify-staging
push_arguments: >-
--vars-file deploy-config/staging.yml
--var DANGEROUS_SALT="$DANGEROUS_SALT"
--var SECRET_KEY="$SECRET_KEY"
--var ADMIN_CLIENT_USERNAME="notify-admin"
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
- name: Deploy to cloud.gov
uses: cloud-gov/cg-cli-tools@main
env:
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
ADMIN_CLIENT_USERNAME: "notify-admin"
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
COMMIT_HASH: ${{ github.sha }}
LOGIN_PEM: ${{ secrets.LOGIN_PEM }}
LOGIN_DOT_GOV_CLIENT_ID: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov"
LOGIN_DOT_GOV_USER_INFO_URL: "https://secure.login.gov/api/openid_connect/userinfo"
LOGIN_DOT_GOV_ACCESS_TOKEN_URL: "https://secure.login.gov/api/openid_connect/token"
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-staging.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-staging.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
API_PUBLIC_URL: ${{ secrets.API_PUBLIC_URL }}
API_PUBLIC_WS_URL: ${{ secrets.API_PUBLIC_WS_URL }}
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
cf_org: gsa-tts-benefits-studio
cf_space: notify-staging
cf_command: >-
push -f manifest.yml
--vars-file deploy-config/staging.yml
--var DANGEROUS_SALT="$DANGEROUS_SALT"
--var SECRET_KEY="$SECRET_KEY"
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
--var COMMIT_HASH="$COMMIT_HASH"
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
--var LOGIN_DOT_GOV_LOGOUT_URL="$LOGIN_DOT_GOV_LOGOUT_URL"
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
--var LOGIN_PEM="$LOGIN_PEM"
--var API_PUBLIC_URL="$API_PUBLIC_URL"
--var API_PUBLIC_WS_URL="$API_PUBLIC_WS_URL"
--strategy rolling
- name: Deploy egress proxy
uses: ./.github/actions/deploy-proxy
env:
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
with:
cf_org: gsa-tts-benefits-studio
cf_space: notify-staging
app: notify-admin-staging
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v34
with:
files: |
deploy-config/egress_proxy/notify-admin-staging.*.acl
.github/actions/deploy-proxy/action.yml
.github/workflows/deploy.yml
- name: Deploy egress proxy
if: steps.changed-egress-config.outputs.any_changed == 'true'
uses: ./.github/actions/deploy-proxy
with:
cf_space: notify-staging
app: notify-admin-staging
bail:
runs-on: ubuntu-latest

View File

@@ -13,37 +13,17 @@ jobs:
environment: staging
steps:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
uses: actions/checkout@v3
- name: Check for drift
uses: dflook/terraform-check@v1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: |
cd terraform/staging
terraform init
terraform plan -detailed-exitcode
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "No changes detected. Intrastructure is up-to-date."
elif [ $exit_code -eq 2 ]; then
echo "Changes detected. Infrastructure drift found."
exit 1
else
echo "Error running terraform plan."
exit $exit_code
fi
with:
path: terraform/staging
check_demo_drift:
runs-on: ubuntu-latest
@@ -51,38 +31,19 @@ jobs:
environment: demo
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
ref: 'production'
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for drift
uses: dflook/terraform-check@v1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: |
cd terraform/demo
terraform init
terraform plan -detailed-exitcode
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "No changes detected. Intrastructure is up-to-date."
elif [ $exit_code -eq 2 ]; then
echo "Changes detected. Infrastructure drift found."
exit 1
else
echo "Error running terraform plan."
exit $exit_code
fi
with:
path: terraform/demo
check_prod_drift:
runs-on: ubuntu-latest
@@ -90,35 +51,16 @@ jobs:
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
ref: 'production'
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for drift
uses: dflook/terraform-check@v1
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: |
cd terraform/production
terraform init
terraform plan -detailed-exitcode
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "No changes detected. Intrastructure is up-to-date."
elif [ $exit_code -eq 2 ]; then
echo "Changes detected. Infrastructure drift found."
exit 1
else
echo "Error running terraform plan."
exit $exit_code
fi
with:
path: terraform/production

View File

@@ -16,15 +16,7 @@ jobs:
environment: demo
steps:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
uses: actions/checkout@v2
- name: Terraform format
id: format
@@ -59,7 +51,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v7
uses: actions/github-script@v6
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

View File

@@ -16,15 +16,7 @@ jobs:
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
uses: actions/checkout@v2
- name: Terraform format
id: format
@@ -59,7 +51,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v7
uses: actions/github-script@v6
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

View File

@@ -16,15 +16,7 @@ jobs:
environment: staging
steps:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
uses: actions/checkout@v2
- name: Terraform format
id: format
@@ -59,7 +51,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v7
uses: actions/github-script@v6
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

23
.gitignore vendored
View File

@@ -14,16 +14,6 @@
*.XLSX
## Non user files allowed to be commited
!app/assets/pdf/wa_case_study.pdf
!app/assets/pdf/best-practices-for-texting-the-public.pdf
!app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf
!app/assets/pdf/best-practices-section-outline.pdf
!app/assets/pdf/standing-up-your-own-notify.pdf
!app/assets/pdf/tcpa_overview.pdf
!app/assets/pdf/investing-notifications-tts-public-benefits-memo.pdf
!app/assets/pdf/out-of-pilot-announcement.pdf
!app/assets/pdf/studio-research-snapshot-2022-07-external.pdf
!app/assets/pdf/TCPA-Overview.pdf
!tests/test_pdf_files/no_eof_marker.pdf
!tests/test_pdf_files/multi_page_pdf.pdf
!tests/test_pdf_files/big.pdf
@@ -133,19 +123,6 @@ app/templates/vendor
secrets.auto.tfvars
terraform.tfstate
terraform.tfstate.backup
requirements.txt
terraform/sandbox/requirements.txt
# Playwright
playwright/
# Pyenv
.python-version
# Nodenv
.node-version
# BackstopJS
backstop_data/bitmaps_test/
backstop_data/html_report/
backstop_data/engine_scripts/playwright/

2
.nvmrc
View File

@@ -1 +1 @@
22.3.0
16.15.1

View File

@@ -1,10 +1,7 @@
{
"defaults": {
"standard": "WCAG2AA",
"runners": ["htmlcs"],
"chromeLaunchConfig": {
"executablePath": "/usr/bin/google-chrome"
},
"runners": ["htmlcs"],
"concurrency": 1,
"hideElements": [
"nav > ol a",

View File

@@ -1,25 +0,0 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
exclude: ^app/assets/pdf/.*\.pdf$
- id: debug-statements
- id: check-merge-conflict
- id: check-toml
- id: check-ast
- id: fix-byte-order-marker
- id: detect-aws-credentials
args: [--allow-missing-credentials]
- id: detect-private-key
- id: mixed-line-ending
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline', '.ds.baseline']

View File

@@ -1,3 +0,0 @@
{
"singleQuote": true
}

View File

@@ -4,6 +4,3 @@
##
export NEW_RELIC_PROXY_HOST=$egress_proxy
export http_proxy=$egress_proxy
export https_proxy=$egress_proxy
export no_proxy="apps.internal,s3-fips.us-gov-west-1.amazonaws.com"

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.9

View File

@@ -6,7 +6,6 @@ APP_VERSION_FILE = app/version.py
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV)
@@ -15,25 +14,10 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
## DEVELOPMENT
.PHONY: bootstrap
bootstrap: ## Set up everything to run the application
make generate-version-file
poetry sync --no-root
bootstrap: generate-version-file ## Set up everything to run the app
poetry install
poetry run playwright install --with-deps
poetry run pre-commit install
source $(NVMSH) --no-use && nvm install && npm install
source $(NVMSH) && npm ci --no-audit
source $(NVMSH) && npm run build
.PHONY: bootstrap-with-git-hooks
bootstrap-with-git-hooks: ## Sets everything up and accounts for pre-existing git hooks
make generate-version-file
poetry sync --no-root
poetry run playwright install --with-deps
git config --global --unset-all core.hooksPath
poetry run pre-commit install
git config --global core.hookspath "${GIT_HOOKS_PATH}"
source $(NVMSH) --no-use && nvm install && npm install
source $(NVMSH) && npm ci --no-audit
source $(NVMSH) --no-use && nvm install && npm ci --no-audit
source $(NVMSH) && npm run build
.PHONY: watch-frontend
@@ -52,10 +36,6 @@ run-flask-bare: ## Run flask without invoking poetry so we can override ENV var
npm-audit: ## Check for vulnerabilities in NPM packages
source $(NVMSH) && npm run audit
.PHONY: npm-audit-fix
npm-audit-fix: ## Fix vulnerabilities that do not require attentino (according to npm)
source $(NVMSH) && npm audit fix
.PHONY: help
help:
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@@ -65,7 +45,7 @@ generate-version-file: ## Generates the app version file
@echo -e "__git_commit__ = \"${GIT_COMMIT}\"\n__time__ = \"${DATE}\"" > ${APP_VERSION_FILE}
.PHONY: test
test: py-lint py-test js-test ## Run tests
test: py-lint py-test js-lint js-test ## Run tests
.PHONY: py-lint
py-lint: ## Run python linting scanners and black
@@ -74,13 +54,6 @@ py-lint: ## Run python linting scanners and black
poetry run flake8 .
poetry run isort --check-only ./app ./tests
.PHONY: tada
tada: ## Run python linting scanners and black
poetry run isort ./app ./tests
poetry run black .
poetry run flake8 .
.PHONY: avg-complexity
avg-complexity:
echo "*** Shows average complexity in radon of all code ***"
@@ -94,19 +67,18 @@ too-complex:
.PHONY: py-test
py-test: export NEW_RELIC_ENVIRONMENT=test
py-test: ## Run python unit tests
poetry run coverage run -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
poetry run coverage report --fail-under=93
poetry run coverage run --omit=*/notifications_utils/* -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
poetry run coverage report --fail-under=96
poetry run coverage html -d .coverage_cache
.PHONY: dead-code
dead-code: ## 60% is our aspirational goal, but currently breaks the build
poetry run vulture ./app ./notifications_utils --min-confidence=100
dead-code:
poetry run vulture ./app --min-confidence=100
.PHONY: e2e-test
e2e-test: export NEW_RELIC_ENVIRONMENT=test
e2e-test: ## Run end-to-end integration tests; note that --browser webkit isn't currently working
@echo "Running E2E tests in path: $${TESTPATH:-tests/end_to_end}"
@bash -c 'DEBUG=pw:api,pw:browser poetry run pytest -vv --browser chromium --browser firefox "$${TESTPATH:-tests/end_to_end}"'
e2e-test: ## Run end-to-end integration tests
poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
.PHONY: js-lint
js-lint: ## Run javascript linting scanners
@@ -120,21 +92,16 @@ js-test: ## Run javascript unit tests
fix-imports: ## Fix imports using isort
poetry run isort ./app ./tests
.PHONY: py-lock
py-lock: ## Syncs dependencies and updates lock file without performing recursive internal updates
poetry lock --no-update
poetry install --sync
.PHONY: freeze-requirements
freeze-requirements: ## create static requirements.txt
poetry export --output requirements.txt
poetry export --without-hashes --format=requirements.txt > requirements.txt
.PHONY: pip-audit
pip-audit:
poetry requirements > requirements.txt
poetry requirements --dev > requirements_for_test.txt
poetry run pip-audit -r requirements.txt
poetry run pip-audit -r requirements_for_test.txt
-poetry run pip-audit -r requirements_for_test.txt
.PHONY: audit
audit: npm-audit pip-audit
@@ -159,7 +126,18 @@ clean:
upload-static:
aws s3 cp --region us-west-2 --recursive --cache-control max-age=315360000,immutable ./app/static s3://${DNS_NAME}-static
.PHONY: test-single
test-single: export NEW_RELIC_ENVIRONMENT=test
test-single: ## Run a single test file
poetry run pytest $(TEST_FILE)
# .PHONY: cf-failwhale-deployed
# cf-failwhale-deployed:
# @cf app notify-admin-failwhale --guid || (echo "notify-admin-failwhale is not deployed on ${CF_SPACE}" && exit 1)
# .PHONY: enable-failwhale
# enable-failwhale: cf-target cf-failwhale-deployed ## Enable the failwhale app and disable admin
# @cf map-route notify-admin-failwhale ${DNS_NAME} --hostname www
# @cf unmap-route notify-admin ${DNS_NAME} --hostname www
# @echo "Failwhale is enabled"
# .PHONY: disable-failwhale
# disable-failwhale: cf-target cf-failwhale-deployed ## Disable the failwhale app and enable admin
# @cf map-route notify-admin ${DNS_NAME} --hostname www
# @cf unmap-route notify-admin-failwhale ${DNS_NAME} --hostname www
# @echo "Failwhale is disabled"

531
README.md
View File

@@ -1,10 +1,6 @@
![notify-logo](https://github.com/GSA/notifications-admin/assets/4156602/31b74039-4d87-4f89-a35e-71e9f3152342)
# Notify UI
# Notify.gov Admin UI
This is the Notify front-end for government users and admins. To see it in
action, check out [the demo site](https://notify-demo.app.cloud.gov)
(contact team for credentials).
This is the Notify front-end for government users and admins. To see it in action, check out [the demo site](https://notify-demo.app.cloud.gov) (contact team for credentials).
Through the interface, users can:
@@ -13,453 +9,69 @@ Through the interface, users can:
- Send batch SMS by uploading a CSV
- View their history of notifications
The [Notify.gov API](https://github.com/GSA/notifications-api) provides the
UI's backend and is required for most things to function. Set that up first!
The [Notify API](https://github.com/GSA/notifications-api) provides the UI's backend and is required for most things to function. Set that up first!
Our other repositories are:
## Local setup
- [notifications-admin](https://github.com/GSA/notifications-admin)
- [us-notify-compliance](https://github.com/GSA/us-notify-compliance/)
- [notify-python-demo](https://github.com/GSA/notify-python-demo)
### Common steps
## Before You Start
1. Install pre-requisites for setup (on a Mac):
- Install XCode or at least the XCode Command Line Tools
- [Homebrew](https://brew.sh/) (follow instructions on page)
- [jq](https://stedolan.github.io/jq/): `brew install jq`
- [terraform](https://www.terraform.io/): `brew install terraform` or `brew install tfenv` and use `tfenv` to install `terraform ~> 1.4.0`
- [cf-cli@8](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html): `brew install cloudfoundry/tap/cf-cli@8`
1. [Log into cloud.gov](https://cloud.gov/docs/getting-started/setup/#set-up-the-command-line): `cf login -a api.fr.cloud.gov --sso`
1. Ensure you have access to the `notify-local-dev` and `notify-staging` spaces in cloud.gov
1. Run the API setup steps
1. Run the development terraform with:
You will need the following items:
```
$ cd terraform/development
$ ./run.sh
```
- An active cloud.gov account with the correct permissions - speak with your
onboarding buddy for help with
[setting up an account](https://cloud.gov/sign-up/) (requires a `.mil`,
`.gov`, or `.fed.us` email address) and getting access to the
`notify-local-dev` and `notify-staging` spaces.
- Admin privileges and SSH access on your machine; you may need to work with
your organization's IT support staff if you're not sure or don't currently
have this access.
1. If you want to send data to New Relic from your local develpment environment, set `NEW_RELIC_LICENSE_KEY` within `.env`
1. Follow the instructions for either `Direct installation` or `Docker installation` below
**In addition, you should set up the Notify.gov API first!**
### Direct installation
[Follow the instructions here to set up the Notify.gov API.](https://github.com/GSA/notifications-api#before-you-start)
1. Get the API running
The Notify.gov API is required in order for the Notify.gov Admin UI to run, and
it will also take care of many of the steps that are listed here. The sections
that are a repeat from the API setup are flagged with an **[API Step]** label
in front of them.
1. Install [poetry](https://python-poetry.org/docs/#installation)
## Local Environment Setup
1. Install [nvm (Node Version Manager)](https://github.com/nvm-sh/nvm#installing-and-updating)
This project is set up to work with
[nvm (Node Version Manager)](https://github.com/nvm-sh/nvm#installing-and-updating)
for managing and using Node.js (version 22.3.0) and the `npm` package manager.
1. Install Python and Node dependencies
These instructions will walk you through how to set your machine up with all of
the required tools for this project.
`make bootstrap`
If you run into certificate errors at the `playwright install` step, try doing this:
1. Run `brew --prefix` to see Homebrew's root directory
1. Create or modify the local `.env` file in the project and add this line:
`NODE_EXTRA_CA_CERTS=/CHANGE-TO-HOMEBREW-INSTALL-PATH/etc/ca-certificates/cert.pem`
Make sure to change `CHANGE-TO-HOMEBREW-INSTALL-PATH` to the path given by `brew --prefix` in the step above.
For example, if `brew --prefix` gave `/opt/homebrew` as output, then the line would look like this:
`NODE_EXTRA_CA_CERTS=/opt/homebrew/etc/ca-certificates/cert.pem`
1. Save the changes to the `.env` file
1. Run `make bootstrap` again
1. Run the Flask server
`make run-flask`
1. Go to http://localhost:6012
### Docker installation
If you are using VS Code, there are also instructions for
[running inside Docker](./docs/docker-remote-containers.md).
### [API Step] Project Pre-Requisite Setup
On MacOS, using [Homebrew](https://brew.sh/) for package management is highly
recommended. This helps avoid some known installation issues. Start by following
the installation instructions on the Homebrew homepage.
**Note:** You will also need Xcode or the Xcode Command Line Tools installed. The
quickest way to do this is is by installing the command line tools in the shell:
```sh
xcode-select -install
```
#### [API Step] Homebrew Setup
If this is your first time installing Homebrew on your machine, you may need to
add its binaries to your system's `$PATH` environment variable so that you can
use the `brew` command. Try running `brew help` to see if Homebrew is
recognized and runs properly. If that fails, then you'll need to add a
configuration line to wherever your `$PATH` environment variable is set.
Your system `$PATH` environment variable is likely set in one of these
locations:
For BASH shells:
- `~/.bashrc`
- `~/.bash_profile`
- `~/.profile`
For ZSH shells:
- `~/.zshrc`
- `~/.zprofile`
There may be different files that you need to modify for other shell
environments.
Which file you need to modify depends on whether or not you are running an
interactive shell or a login shell
(see [this Stack Overflow post](https://stackoverflow.com/questions/18186929/what-are-the-differences-between-a-login-shell-and-interactive-shell)
for an explanation of the differences). If you're still not sure, please ask
the team for help!
Once you determine which file you'll need to modify, add these lines before any
lines that add or modify the `$PATH` environment variable; near or at the top
of the file is appropriate:
```sh
# Homebrew setup
eval "$(/opt/homebrew/bin/brew shellenv)"
```
This will make sure Homebrew gets setup correctly. Once you make these changes,
either start a new shell session or source the file
(`source ~/.FILE-YOU-MODIFIED`) you modified to have your system recognize the
changes.
Verify that Homebrew is now working by trying to run `brew help` again.
### [API Step] System-Level Package Installation
There are several packages you will need to install for your system in order to
get the app running (and these are good to have in general for any software
development).
Start off with these packages since they're quick and don't require additional
configuration after installation to get working out of the box:
- [jq](https://stedolan.github.io/jq/) - for working with JSON in the command
line
- [git](https://git-scm.com/) - for version control management
- [tenv](https://github.com/tofuutils/tenv) - for managing
[Terraform](https://www.terraform.io/) installations
- [cf-cli@8](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html) - for
working with a Cloud Foundry platform (e.g., cloud.gov)
- [vim](https://www.vim.org/) - for editing files more easily in the command
line
- [wget](https://www.gnu.org/software/wget/) - for retrieving files in the
command line
You can install them by running the following:
```sh
brew install jq git tfenv cloudfoundry/tap/cf-cli@8 vim wget
```
#### [API Step] Terraform Installation
As a part of the installation above, you just installed `tenv` to manage
Terraform installations. This is great, but you still need to install Terraform
itself, which can be done with this command:
```sh
tenv
```
This will open a menu for you; choose Terraform, then choose the latest stable
version.
_NOTE: This project currently uses the latest `1.12.x release of Terraform._
#### [API Step] Python Installation
Now we're going to install a tool to help us manage Python versions and
virtual environments on our system. First, we'll install
[pyenv](https://github.com/pyenv/pyenv) and one of its plugins,
[pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv), with Homebrew:
```sh
brew install pyenv pyenv-virtualenv
```
When these finish installing, you'll need to make another adjustment in the
file that you adjusted for your `$PATH` environment variable and Homebrew's
setup. Open the file, and add these lines to it:
```
# pyenv setup
export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
```
Once again, start a new shell session or source the file in your current shell
session to make the changes take effect.
Now we're ready to install the Python version we need with `pyenv`, like so:
```sh
pyenv install 3.12
```
This will install the latest version of Python 3.12.
_NOTE: This project currently runs on Python 3.12.x._
#### [API Step] Python Dependency Installation
Lastly, we need to install the tool we use to manage Python dependencies within
the project, which is [poetry](https://python-poetry.org/).
Visit the
[official installer instructions page](https://python-poetry.org/docs/#installing-with-the-official-installer)
and follow the steps to install Poetry directly with the script.
This will ensure `poetry` doesn't conflict with any project virtual environments
and can update itself properly.
#### Node.js and npm Installation
The project will manage most of the Node.js pieces for you, but you will need to
install
[nvm (Node Version Manager)](https://github.com/nvm-sh/nvm#installing-and-updating)
in order for things to work.
Follow the steps in the installation instructions to get `nvm` installed with
the install script they provide, and double check that the file that you
adjusted for your `$PATH` environment variable and Homebrew's setup is properly
updated with the `nvm` setup as well.
Open the file and check for the `nvm` setup lines; if they're not there, then
add the lines found in the
[nvm installation instructions](https://github.com/nvm-sh/nvm#install--update-script)
to to the file after the `pyenv` section you created, e.g.:
```
# nvm setup
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
```
### First-Time Project Setup
Once all of pre-requisites for the project are installed and you have a
cloud.gov account, you can now set up the admin project and get things running
locally!
First, clone the repository in the directory of your choosing on your machine:
```sh
git clone git@github.com:GSA/notifications-admin.git
```
Now go into the project directory (`notifications-admin` by default), create a
virtual environment, and set the local Python version to point to the virtual
environment (assumes version Python `3.12.9` is what is installed on your
machine):
```sh
cd notifications-admin
pyenv virtualenv 3.12.9 notify-admin
pyenv local notify-admin
```
_If you're not sure which version of Python was installed with `pyenv`, you can check by running `pyenv versions` and it'll list everything available currently._
Now [log into cloud.gov](https://cloud.gov/docs/getting-started/setup/#set-up-the-command-line)
in the command line by using this command:
```sh
cf login -a api.fr.cloud.gov --sso
```
_REMINDER: Ensure you have access to the `notify-local-dev` and `notify-staging` spaces in cloud.gov_
Now run the development Terraform setup by navigating to the development
folder and running the script in it:
```sh
cd terraform/development
./run.sh
```
In addition to some infrastructure setup, this will also create a local `.env`
file for you in the project's root directory, which will include a handful of
project-specific environment variables.
#### Upgrading Python in existing projects
If you're upgrading an existing project to a newer version of Python, you can
follow these steps to get yourself up-to-date.
First, use `pyenv` to install the newer version of Python you'd like to use;
we'll use `3.12` in our example here since we recently upgraded to this version:
```sh
pyenv install 3.12
```
Next, delete the virtual environment you previously had set up. If you followed
the instructions above with the first-time set up, you can do this with `pyenv`:
```sh
pyenv virtualenv-delete notify-admin
```
Now, make sure you are in your project directory and recreate the same virtual
environment with the newer version of Python you just installed:
```sh
cd notifications-admin
pyenv virtualenv 3.12.9 notify-admin
pyenv local notify-admin
```
At this point, proceed with the rest of the instructions here in the README and
you'll be set with an upgraded version of Python.
_If you're not sure about the details of your current virtual environment, you can run `poetry env info` to get more information. If you've been using `pyenv` for everything, you can also see all available virtual environments with `pyenv virtualenvs`._
#### Poetry upgrades ####
If you are doing a new project setup, then after you install poetry you need to install the export plugin
```sh
poetry self add poetry-plugin-export
```
If you are upgrading from poetry 1.8.5, you need to do this:
```sh
curl -sSL https://install.python-poetry.org | python3 - --version 2.1.3
poetry self add poetry-export-plugin
```
#### Updating the .env file for Login.gov
To configure the application for Login.gov, you will need to update the following environment variables in the .env file:
```
COMMIT_HASH=”--------”
```
Reach out to someone on the team to get the most recent Login.gov key.
```
LOGIN_PEM="INSERT_LOGIN_GOV_KEY_HERE"
```
#### Updating the .env file for E2E tests
With the newly created `.env` file in place, you'll need to make one more
adjustment in it for things to run properly for the E2E tests.
Open the `.env` file and look for the `NODE_EXTRA_CA_CERTS` environment variable
toward the top. You will need to modify its value to be the path to Homebrew's
CA certificate file.
Run `brew --prefix` to see Homebrew's root directory, and use that value to
update the `NODE_EXTRA_CA_CERTS` environment variable with the path. For
example, if `brew --prefix` gave `/opt/homebrew` as output, then the line would
look like this:
```
NODE_EXTRA_CA_CERTS=/opt/homebrew/etc/ca-certificates/cert.pem
```
Save this change to the `.env` file and you'll be squared away.
## Running the Project and Routine Maintenance
The first time you run the project you'll need to run the project setup from the
root project directory:
```sh
make bootstrap
```
This command is handled by the `Makefile` file in the root project directory, as
are a few others.
_NOTE: You'll want to occasionally run `make bootstrap` to keep your project up-to-date, especially when there are dependency updates._
Now you can run the web server and background workers for asynchronous jobs:
```sh
make run-flask
```
This will run the local development web server and make the admin site
available at http://localhost:6012; remember to make sure that the Notify.gov
API is running as well!
## Creating a 'First User' in the database
After you have completed all setup steps, you will be unable to log in, because there
will not be a user in the database to link to the login.gov account you are using. So
you will need to create that user in your database using the 'create-test-user' command.
Open a terminal pointing to the api project and then run this command.
```poetry run flask command create-test-user --admin=True```
Supply your name, email address, mobile number, and password when prompted. Make sure the email address
is the same one you are using in login.gov and make sure your phone number is in the format 5555555555.
If for any reason in the course of development it is necessary for your to delete your db
via the `dropdb` command, you will need to repeat these steps when you recreate your db.
## Git Hooks
We're using [`pre-commit`](https://pre-commit.com/) to manage hooks in order to
automate common tasks or easily-missed cleanup. It's installed as part of
`make bootstrap` and is limited to this project's virtualenv.
To run the hooks in advance of a `git` operation, use
`poetry run pre-commit run`. For running across the whole codebase (useful after
adding a new hook), use `poetry run pre-commit run --all-files`.
The configuration is stored in `.pre-commit-config.yaml`. In that config, there
are links to the repos from which the hooks are pulled, so hop through there if
ou want a detailed description of what each one is doing.
We do not maintain any hooks in this repository.
## Python Dependency Management
We're using [`Poetry`](https://python-poetry.org/) for managing our Python
dependencies and local virtual environments. When it comes to managing the
Python dependencies, there are a couple of things to bear in mind.
For situations where you manually manipulate the `pyproject.toml` file, you
should use the `make py-lock` command to sync the `poetry.lock` file. This will
ensure that you don't inadvertently bring in other transitive dependency updates
that have not been fully tested with the project yet.
If you're just trying to update a dependency to a newer (or the latest) version,
you should let Poetry take care of that for you by running the following:
```sh
poetry update <dependency> [<dependency>...]
```
You can specify more than one dependency together. With this command, Poetry
will do the following for you:
- Find the latest compatible version(s) of the specified dependency/dependencies
- Install the new versions
- Update and sync the `poetry.lock` file
In either situation, once you are finished and have verified the dependency
changes are working, please be sure to commit both the `pyproject.toml` and
`poetry.lock` files.
## Known Installation Issues
### Python Installation Errors
On M1 Macs, if you get a `fatal error: 'Python.h' file not found` message, try a
different method of installing Python. The recommended approach is to use
[`pyenv`](https://github.com/pyenv/pyenv), as noted above in the installation
instructions.
If you're using PyCharm for Python development, we've noticed some quirkiness
with the IDE and the interaction between Poetry and virtual environment
management that could cause a variety of problems to come up during project
setup and dependency management. Other tools, such as Visual Studio Code, have
proven to be a smoother experience for folks.
If you are using VS Code, there are also instructions for [running inside Docker](./docs/docker-remote-containers.md)
## To test the application
@@ -473,13 +85,11 @@ make test
npm run test-watch
```
To run a specific JavaScript test, you'll need to copy the full command from
`package.json`.
To run a specific JavaScript test, you'll need to copy the full command from `package.json`.
## Running a11y-scans locally
Unlike most of the tests and scans, pa11y-ci cannot currently be run from within
the VSCode dev container.
Unlike most of the tests and scans, pa11y-ci cannot currently be run from within the VSCode dev container.
1. `make run-flask` from within the devcontainer
2. Run `make a11y-scan` from your host computer.
@@ -492,41 +102,16 @@ the VSCode dev container.
## License && public domain
Work through
[commit `543be77`](https://github.com/GSA/notifications-admin/commit/543be77776b64fddb6ba70fbb015ecd81a372478)
is licensed by the UK government under the MIT license. Work after that commit
is in the worldwide public domain. See [LICENSE.md](./LICENSE.md) for more
information.
Work through [commit `543be77`](https://github.com/GSA/notifications-admin/commit/543be77776b64fddb6ba70fbb015ecd81a372478) is licensed by the UK government under the MIT license. Work after that commit is in the worldwide public domain. See [LICENSE.md](./LICENSE.md) for more information.
## Contributing
As stated in [CONTRIBUTING.md](CONTRIBUTING.md), all contributions to this
project will be released under the CC0 dedication. By submitting a pull request,
you are agreeing to comply with this waiver of copyright interest.
As stated in [CONTRIBUTING.md](CONTRIBUTING.md), all contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.
## About the TTS Public Benefits Studio
The Public Benefits Studio is a team inside of
[GSAs Technology Transformation Services](https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services)
(TTS), home to innovative programs like [18F](https://18f.gsa.gov/) and
[Login.gov](https://login.gov). We collaborate with benefits programs to develop
shared technology tools and best practices that reduce the burden of navigating
government programs for low income individuals and families.
The Public Benefits Studio is a team inside of [GSAs Technology Transformation Services](https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services) (TTS), home to innovative programs like [18F](https://18f.gsa.gov/) and [Login.gov](https://login.gov). We collaborate with benefits programs to develop shared technology tools and best practices that reduce the burden of navigating government programs for low income individuals and families.
Were a cross-functional team of technologists with specialized experience
working across public benefits programs like Medicaid, SNAP, and unemployment
insurance.
Were a cross-functional team of technologists with specialized experience working across public benefits programs like Medicaid, SNAP, and unemployment insurance.
For more information on what we're working on, the Notify tool, and how to get
involved with our team,
[see our flyer.](https://github.com/GSA/notifications-admin/blob/main/docs/notify-pilot-flyer.md)
## Updating secrets for the E2E tests
At some point, E2E tests will fail because the secrets held in VCAP_SERVICES have expired. To refresh
them, you will need to do the following:
1. Log in the normal way to access cloudfoundry command line options
2. In your terminal, run `chmod +x print_vcap.sh`
3. In your terminal, run `./print_vcap.sh`
4. Copy the value in your terminal and paste it into the VCAP_SERVICES secret in Github on the staging tier.
For more information on what we're working on, the Notify tool, and how to get involved with our team, [see our flyer.](https://github.com/GSA/notifications-admin/blob/main/docs/notify-pilot-flyer.md)

View File

@@ -1,2 +0,0 @@
Contact: mailto:notify-support@gsa.gov
Expires: 2035-10-15T23:59:59Z

View File

@@ -1,11 +1,8 @@
import os
import pathlib
import re
import secrets
import sys
from functools import partial
from time import monotonic
from urllib.parse import unquote, urlparse, urlunparse
from urllib.parse import urlparse, urlunparse
import jinja2
from flask import (
@@ -24,7 +21,15 @@ from flask_login import LoginManager, current_user
from flask_talisman import Talisman
from flask_wtf import CSRFProtect
from flask_wtf.csrf import CSRFError
from govuk_frontend_jinja.flask_ext import init_govuk_frontend
from itsdangerous import BadSignature
from notifications_python_client.errors import HTTPError
from notifications_utils import logging, request_helper
from notifications_utils.formatters import (
formatted_list,
get_lines_with_normalised_whitespace,
)
from notifications_utils.recipients import format_phone_number_human_readable
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
from werkzeug.exceptions import abort
from werkzeug.local import LocalProxy
@@ -32,10 +37,8 @@ from werkzeug.local import LocalProxy
from app import proxy_fix
from app.asset_fingerprinter import asset_fingerprinter
from app.config import configs
from app.extensions import redis_client
from app.extensions import redis_client, zendesk_client
from app.formatters import (
convert_markdown_template,
convert_time_unixtimestamp,
convert_to_boolean,
format_auth_type,
format_billions,
@@ -49,8 +52,7 @@ from app.formatters import (
format_datetime_human,
format_datetime_normal,
format_datetime_relative,
format_datetime_scheduled_notification,
format_datetime_table,
format_datetime_short,
format_day_of_week,
format_delta,
format_delta_days,
@@ -86,12 +88,12 @@ from app.navigation import (
HeaderNavigation,
MainNavigation,
OrgNavigation,
SecondaryNavigation,
)
from app.notify_client import InviteTokenError
from app.notify_client.api_key_api_client import api_key_api_client
from app.notify_client.billing_api_client import billing_api_client
from app.notify_client.complaint_api_client import complaint_api_client
from app.notify_client.email_branding_client import email_branding_client
from app.notify_client.events_api_client import events_api_client
from app.notify_client.inbound_number_client import inbound_number_client
from app.notify_client.invite_api_client import invite_api_client
@@ -102,35 +104,25 @@ from app.notify_client.organizations_api_client import organizations_client
from app.notify_client.performance_dashboard_api_client import (
performance_dashboard_api_client,
)
from app.notify_client.platform_stats_api_client import (
platform_stats_api_client,
)
from app.notify_client.platform_stats_api_client import platform_stats_api_client
from app.notify_client.provider_client import provider_client
from app.notify_client.service_api_client import service_api_client
from app.notify_client.status_api_client import status_api_client
from app.notify_client.template_folder_api_client import (
template_folder_api_client,
)
from app.notify_client.template_statistics_api_client import (
template_statistics_client,
)
from app.notify_client.template_folder_api_client import template_folder_api_client
from app.notify_client.template_statistics_api_client import template_statistics_client
from app.notify_client.upload_api_client import upload_api_client
from app.notify_client.user_api_client import user_api_client
from app.url_converters import SimpleDateTypeConverter, TemplateTypeConverter
from app.utils.api_health import is_api_down
from app.utils.govuk_frontend_jinja.flask_ext import init_govuk_frontend
from notifications_python_client.errors import HTTPError
from notifications_utils import logging, request_helper
from notifications_utils.formatters import (
formatted_list,
get_lines_with_normalised_whitespace,
from app.url_converters import (
SimpleDateTypeConverter,
TemplateTypeConverter,
TicketTypeConverter,
)
from notifications_utils.recipients import format_phone_number_human_readable
from notifications_utils.url_safe_token import generate_token
login_manager = LoginManager()
csrf = CSRFProtect()
talisman = Talisman()
# The current service attached to the request stack.
current_service = LocalProxy(partial(getattr, request_ctx, "service"))
@@ -142,90 +134,30 @@ navigation = {
"main_navigation": MainNavigation(),
"header_navigation": HeaderNavigation(),
"org_navigation": OrgNavigation(),
"secondary_navigation": SecondaryNavigation(),
}
def _csp(config):
asset_domain = config["ASSET_DOMAIN"]
api_public_url = config["API_PUBLIC_URL"]
api_public_ws_url = config["API_PUBLIC_WS_URL"]
logo_domain = config["LOGO_CDN_DOMAIN"]
return {
"default-src": ["'self'", asset_domain],
"frame-src": [
"https://www.youtube.com",
"https://www.youtube-nocookie.com",
"https://www.googletagmanager.com",
],
"frame-ancestors": "'none'",
"form-action": "'self'",
"script-src": [
"'self'",
asset_domain,
"'unsafe-eval'",
"https://js-agent.newrelic.com",
"https://gov-bam.nr-data.net",
"https://www.googletagmanager.com",
"https://www.google-analytics.com",
"https://dap.digitalgov.gov",
"https://cdn.socket.io",
],
"connect-src": [
"'self'",
"https://gov-bam.nr-data.net",
"https://www.google-analytics.com",
f"{api_public_url}",
f"{api_public_ws_url}",
],
"connect-src": ["'self'", "https://gov-bam.nr-data.net"],
"style-src": ["'self'", asset_domain],
"img-src": ["'self'", asset_domain],
"img-src": ["'self'", asset_domain, logo_domain],
}
def create_app(application):
@application.after_request
def add_security_headers(response):
response.headers["Cross-Origin-Embedder-Policy"] = "credentialless"
return response
@application.context_processor
def inject_feature_flags():
# this is where feature flags can be easily added as a dictionary within context
feature_socket_enabled = application.config.get("FEATURE_SOCKET_ENABLED", False)
return dict(
FEATURE_SOCKET_ENABLED=feature_socket_enabled,
)
@application.context_processor
def inject_is_api_down():
return {"is_api_down": is_api_down()}
@application.context_processor
def inject_initial_signin_url():
ttl = 24 * 60 * 60
# make and store the state
state = generate_token(
str(request.remote_addr),
current_app.config["SECRET_KEY"],
current_app.config["DANGEROUS_SALT"],
)
state_key = f"login-state-{unquote(state)}"
redis_client.set(state_key, state, ex=ttl)
# make and store the nonce
nonce = secrets.token_urlsafe()
nonce_key = f"login-nonce-{unquote(nonce)}"
redis_client.set(nonce_key, nonce, ex=ttl)
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
if url is not None:
url = url.replace("NONCE", nonce)
url = url.replace("STATE", state)
return {"initial_signin_url": url}
notify_environment = os.environ["NOTIFY_ENVIRONMENT"]
application.config.from_object(configs[notify_environment])
@@ -248,6 +180,7 @@ def create_app(application):
api_key_api_client,
billing_api_client,
complaint_api_client,
email_branding_client,
events_api_client,
inbound_number_client,
invite_api_client,
@@ -257,6 +190,7 @@ def create_app(application):
organizations_client,
performance_dashboard_api_client,
platform_stats_api_client,
provider_client,
service_api_client,
status_api_client,
template_folder_api_client,
@@ -265,6 +199,7 @@ def create_app(application):
user_api_client,
# External API clients
redis_client,
zendesk_client,
):
client.init_app(application)
@@ -273,11 +208,14 @@ def create_app(application):
content_security_policy=_csp(application.config),
content_security_policy_nonce_in=["style-src", "script-src"],
permissions_policy={
"accelerometer": '(self "https://www.youtube-nocookie.com")',
"autoplay": '(self "https://www.youtube-nocookie.com")',
"accelerometer": "()",
"ambient-light-sensor": "()",
"autoplay": "()",
"battery": "()",
"camera": "()",
"document-domain": "()",
"geolocation": "()",
"gyroscope": '(self "https://www.youtube-nocookie.com")',
"gyroscope": "()",
"local-fonts": "()",
"magnetometer": "()",
"microphone": "()",
@@ -290,24 +228,6 @@ def create_app(application):
)
logging.init_app(application)
# Hopefully will help identify if there is a race condition causing the CSRF errors
# that we have occasionally seen in our environments.
for key in ("SECRET_KEY", "DANGEROUS_SALT"):
try:
value = application.config[key]
except KeyError:
application.logger.error(f"Env Var {key} doesn't exist.")
else:
try:
data_len = len(value.strip())
except (TypeError, AttributeError):
application.logger.error(f"Env Var {key} invalid type: {type(value)}")
else:
if data_len:
application.logger.info(f"Env Var {key} is a non-zero length.")
else:
application.logger.error(f"Env Var {key} is empty.")
login_manager.login_view = "main.sign_in"
login_manager.login_message_category = "default"
login_manager.session_protection = None
@@ -358,40 +278,19 @@ def init_app(application):
def _attach_current_user():
return {"current_user": current_user}
@application.context_processor
def _attach_enums():
from app.enums import ServicePermission
return {"ServicePermission": ServicePermission}
@application.context_processor
def _nav_selected():
return navigation
@application.context_processor
def _attach_current_global_daily_messages():
global_limit = 0
remaining_global_messages = 0
if current_app:
if request.view_args:
service_id = request.view_args.get(
"service_id", session.get("service_id")
)
else:
service_id = session.get("service_id")
if service_id:
global_limit = current_app.config["GLOBAL_SERVICE_MESSAGE_LIMIT"]
global_messages_count = (
service_api_client.get_global_notification_count(service_id)
)
remaining_global_messages = global_limit - global_messages_count.get(
"count"
)
return {
"global_message_limit": global_limit,
"daily_global_messages_remaining": remaining_global_messages,
}
if current_app:
global_limit = current_app.config["GLOBAL_SERVICE_MESSAGE_LIMIT"]
global_messages_count = service_api_client.get_global_notification_count()
remaining_global_messages = global_limit - global_messages_count
return {"daily_global_messages_remaining": remaining_global_messages}
@application.before_request
def record_start_time():
@@ -409,6 +308,7 @@ def init_app(application):
application.url_map.converters["uuid"].to_python = lambda self, value: value
application.url_map.converters["template_type"] = TemplateTypeConverter
application.url_map.converters["ticket_type"] = TicketTypeConverter
application.url_map.converters["simple_date"] = SimpleDateTypeConverter
@@ -431,23 +331,11 @@ def make_session_permanent():
def create_beta_url(url):
url_created = None
try:
url_created = urlparse(url)
url_list = list(url_created)
url_list[1] = "beta.notify.gov"
url_for_redirect = urlunparse(url_list)
return url_for_redirect
except ValueError:
# This might be happening due to IPv6, see issue # 1395.
# If we see "'RequestContext' object has no attribute 'service'" in the logs
# we can search around that timestamp and find this output, hopefully.
# It may be sufficient to just catch and log, and prevent the stack trace from being in the logs
# but we need to confirm the root cause first.
current_app.logger.error(
f"create_beta_url orig_url: {url} \
url_created = {str(url_created)} url_for_redirect {str(url_for_redirect)}"
)
url_created = urlparse(url)
url_list = list(url_created)
url_list[1] = "beta.notify.gov"
url_for_redirect = urlunparse(url_list)
return url_for_redirect
def redirect_notify_to_beta():
@@ -455,7 +343,6 @@ def redirect_notify_to_beta():
current_app.config["NOTIFY_ENVIRONMENT"] == "production"
and "beta.notify.gov" not in request.url
):
# TODO add debug here to trace what is going on with the URL for the 'RequestContext' error
url_to_beta = create_beta_url(request.url)
return redirect(url_to_beta, 302)
@@ -477,10 +364,6 @@ def load_service_before_request():
request_ctx.service = Service(
service_api_client.get_service(service_id)["data"]
)
stats = service_api_client.get_service_statistics(
service_id, limit_days=7
)
request_ctx.service.stats = stats
except HTTPError as exc:
# if service id isn't real, then 404 rather than 500ing later because we expect service to be set
if exc.status_code == 404:
@@ -538,28 +421,26 @@ def register_errorhandlers(application): # noqa (C901 too complex)
@application.errorhandler(HTTPError)
def render_http_error(error):
error_url = error.response.url if error.response else "unknown URL"
application.logger.warning(
f"API {error_url} failed with status {error.status_code} message {error.message}",
exc_info=sys.exc_info(),
stack_info=True,
"API {} failed with status {} message {}".format(
error.response.url if error.response else "unknown",
error.status_code,
error.message,
)
)
error_code = error.status_code
if error_code not in [401, 404, 403, 410]:
# probably a 500 or 503.
# it might be a 400, which we should handle as if it's an internal server error. If the API might
# legitimately return a 400, we should handle that within the view or the client that calls it.
application.logger.exception(
f"API {error_url} failed with status {error.status_code} message {error.message}",
exc_info=sys.exc_info(),
stack_info=True,
"API {} failed with status {} message {}".format(
error.response.url if error.response else "unknown",
error.status_code,
error.message,
)
)
error_code = 500
return _error_response(error_code)
@application.errorhandler(400)
@@ -644,16 +525,13 @@ def setup_event_handlers():
def add_template_filters(application):
application.add_template_filter(slugify)
for fn in [
format_auth_type,
format_billions,
format_datetime,
format_datetime_24h,
format_datetime_normal,
format_datetime_scheduled_notification,
format_datetime_table,
format_datetime_short,
valid_phone_number,
linkable_name,
format_date,
@@ -680,7 +558,6 @@ def add_template_filters(application):
format_thousands,
id_safe,
convert_to_boolean,
convert_time_unixtimestamp,
format_list_items,
iteration_count,
recipient_count,
@@ -692,7 +569,6 @@ def add_template_filters(application):
format_mobile_network,
format_yes_no,
square_metres_to_square_miles,
convert_markdown_template,
]:
application.add_template_filter(fn)
@@ -704,10 +580,3 @@ def init_jinja(application):
]
jinja_loader = jinja2.FileSystemLoader(template_folders)
application.jinja_loader = jinja_loader
def slugify(text):
"""
Converts text to lowercase, replaces spaces with hyphens, and removes invalid characters.
"""
return re.sub(r"[^a-z0-9-]", "", re.sub(r"\s+", "-", text.lower()))

View File

@@ -17,12 +17,12 @@
href="/static/images/apple-touch-icon.png?a0f7e1b728a42016b247dc54ee40d055">
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
<link rel="manifest" href="{{ asset_url('images/site.webmanifest') }}">
<link rel="manifest" href="/static/images/site.webmanifest">
<link rel="stylesheet" media="screen" href="/static/stylesheets/main.css?d077f86473501ab244de0b30600536ee" />
<link rel="stylesheet" media="print" href="/static/stylesheets/print.css?28010888ca5719dc83d7c2c80f6ed2b2" />
<meta property="og:image" content="/static/images/notify-og-image.png">
<meta property="og:image" content="/static/images/usa-opengraph-image.png">
</head>
<body class="usa-template__body">
@@ -68,8 +68,11 @@
<p class="usa-body">
Try again later.
</p>
<p>To report a problem you can email us at <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>
<p>You can expect a response within one business day.</p>
<p class="usa-body">
You can check our <a class="usa-link"
href="https://status.notifications.service.gov.uk/">system status</a> page to see if there are any known
issues.<br />To report a problem, email <a
href="mailto:gov-uk-notify-support@digital.cabinet-office.gov.uk">gov-uk-notify-support@digital.cabinet-office.gov.uk</a>
</div>
</div>
</main>
@@ -104,4 +107,4 @@
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><g><g><path d="m377.64 347.666c-4.473 0-8.1 3.626-8.1 8.099s3.626 8.1 8.1 8.1c4.473 0 8.099-3.626 8.099-8.1s-3.626-8.099-8.099-8.099z"/><path d="m377.716 334.615c4.149 0 7.511-3.363 7.511-7.511v-67.172c0-4.148-3.362-7.511-7.511-7.511-4.148 0-7.511 3.363-7.511 7.511v67.172c0 4.148 3.362 7.511 7.511 7.511z"/><path d="m377.64 219.085c-7.974 0-15.716 1.011-23.105 2.912v-14.937c0-59.921-37.644-111.208-90.522-131.476 2.741-6.491 4.267-13.614 4.267-21.092 0-30.047-24.445-54.492-54.492-54.492s-54.491 24.445-54.491 54.491c0 7.469 1.522 14.586 4.256 21.071-16.063 6.135-30.994 15.219-44.006 26.957-3.081 2.778-3.325 7.528-.547 10.608 2.781 3.081 7.53 3.324 10.608.546 23.118-20.854 53.014-32.339 84.18-32.339 69.324 0 125.725 56.4 125.725 125.725v20.222c-32.217 14.573-54.688 47.021-54.688 84.618 0 11.997 2.288 23.47 6.45 34.005h-203.212v-138.845c0-24.475 7.036-48.196 20.346-68.599 2.267-3.474 1.288-8.128-2.187-10.394-3.474-2.266-8.129-1.288-10.394 2.186-14.907 22.851-22.787 49.41-22.787 76.807v143.829l-28.543 37.616c-1.903 2.506-2.952 5.62-2.952 8.768v27.183c0 7.996 6.505 14.501 14.501 14.501h94.92c-.468 3.102-.719 6.274-.719 9.504 0 35.036 28.504 63.541 63.541 63.541 35.036 0 63.541-28.504 63.541-63.541 0-3.232-.263-6.402-.735-9.504h29.487c4.149 0 7.511-3.363 7.511-7.511s-3.362-7.511-7.511-7.511h-249.514v-26.486l27.713-36.521h214.585c15.307 24.506 41.72 41.39 72.142 43.548v19.459h-29.946c-4.148 0-7.511 3.363-7.511 7.511s3.362 7.511 7.511 7.511h30.467c7.996 0 14.5-6.505 14.5-14.501v-20.125c47.261-4.255 84.425-44.082 84.425-92.431 0-51.178-41.636-92.814-92.814-92.814zm-127.992-148.137c-11.454-3.02-23.471-4.636-35.86-4.636-12.217 0-24.259 1.583-35.863 4.634-2.311-5.012-3.607-10.584-3.607-16.455 0-21.764 17.706-39.469 39.469-39.469s39.469 17.706 39.469 39.469c.002 5.873-1.295 11.445-3.608 16.457zm-83.427 368.007h95.144c.616 3.074.942 6.251.942 9.504 0 26.753-21.765 48.519-48.519 48.519s-48.519-21.766-48.519-48.519c.001-3.254.335-6.429.952-9.504zm211.419-49.263c-42.895 0-77.793-34.898-77.793-77.793s34.897-77.793 77.793-77.793 77.793 34.898 77.793 77.793c-.001 42.895-34.898 77.793-77.793 77.793z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><g><path d="m468.547 144.82h-63.047v-121.32c0-12.958-10.542-23.5-23.5-23.5h-324c-12.958 0-23.5 10.542-23.5 23.5v84.833c.344 9.931 14.659 9.923 15 0v-33.333h341v69.82h-121.998c-14.312 0-25.954 11.574-25.954 25.8v83.174l-28.866 19.221c-5.511 3.444-5.51 12.168.001 15.611l28.865 19.22v83.174c0 14.226 11.643 25.799 25.954 25.799h121.998v71.68c0 4.687-3.813 8.5-8.5 8.5h-324c-4.687 0-8.5-3.813-8.5-8.5v-20h131.5c7.444 0 13.5-6.056 13.5-13.5v-127.999c0-7.444-6.056-13.5-13.5-13.5h-131.5v-168.021c-.344-9.931-14.659-9.923-15 0v168.021h-3.5c-7.444 0-13.5 6.056-13.5 13.5v128c0 7.444 6.056 13.5 13.5 13.5h3.5v20c0 12.958 10.542 23.5 23.5 23.5h324c12.958 0 23.5-10.542 23.5-23.5v-71.68h11.5c9.931-.344 9.923-14.659 0-15h-148.498c-6.04 0-10.954-4.845-10.954-10.799v-85.173c0-3.779-1.882-7.288-5.031-9.384l-23.492-15.643 23.492-15.644c3.15-2.098 5.031-5.606 5.031-9.384v-85.173c0-5.955 4.914-10.8 10.954-10.8h200.045c6.04 0 10.953 4.845 10.953 10.8v220.401c0 5.955-4.913 10.799-10.953 10.799h-16.436c-9.931.344-9.923 14.659 0 15h16.436c14.311 0 25.953-11.574 25.953-25.799v-220.401c0-14.226-11.643-25.8-25.953-25.8zm-256.047-129.82v9.5c-.353 9.929-14.65 9.922-15 0v-9.5zm-55.333 0v9.5c-.353 9.929-14.65 9.922-15 0v-9.5zm-55.334 0v9.5c-.353 9.929-14.65 9.922-15 0v-9.5zm-52.333 45v-36.5c0-4.687 3.813-8.5 8.5-8.5h13.833v9.5c1.135 29.816 43.876 29.793 45 0v-9.5h10.334v9.5c1.135 29.816 43.876 29.793 45 0v-9.5h10.333v9.5c1.135 29.816 43.876 29.793 45 0v-9.5h154.5c4.687 0 8.5 3.813 8.5 8.5v36.5zm-17 268.5h147v125h-147z"/><path d="m451.356 344.514c2.439-4.507 2.214-9.973-.589-14.264l-69.093-105.771c-5.242-8.361-18.107-8.359-23.35 0l-69.092 105.771c-2.803 4.291-3.028 9.756-.589 14.263s7.139 7.307 12.264 7.307h138.186c5.125 0 9.824-2.799 12.263-7.306zm-148.499-7.694 67.143-102.786 67.143 102.786z"/><path d="m362.5 266.32v31.445c.344 9.931 14.659 9.923 15 0v-31.445c-.344-9.931-14.659-9.923-15 0z"/><path d="m370 310.82c-4.143 0-7.5 3.358-7.5 7.5v2.5c.344 9.931 14.659 9.923 15 0v-2.5c0-4.142-3.357-7.5-7.5-7.5z"/><path d="m91.375 123.094h36.375c9.931-.344 9.923-14.659 0-15h-36.375c-9.931.344-9.923 14.66 0 15z"/><path d="m91.375 149.316h90c9.931-.344 9.923-14.659 0-15h-90c-9.931.344-9.923 14.66 0 15z"/><path d="m91.375 175.539h77.625c9.931-.344 9.923-14.659 0-15h-77.625c-9.931.344-9.923 14.659 0 15z"/><path d="m91.375 225.76h36.375c9.931-.344 9.923-14.659 0-15h-36.375c-9.931.344-9.923 14.66 0 15z"/><path d="m91.375 251.982h90c9.931-.344 9.923-14.659 0-15h-90c-9.931.344-9.923 14.66 0 15z"/><path d="m91.375 278.205h77.625c9.931-.344 9.923-14.659 0-15h-77.625c-9.931.344-9.923 14.659 0 15z"/><path d="m139.354 356.038c-2.93-2.929-7.678-2.929-10.607 0l-22.747 22.747-22.747-22.747c-2.93-2.929-7.678-2.929-10.607 0s-2.929 7.678 0 10.606l22.748 22.748-22.748 22.748c-2.929 2.929-2.929 7.678 0 10.606 2.93 2.929 7.677 2.929 10.607 0l22.747-22.747 22.747 22.747c2.93 2.929 7.677 2.929 10.607 0 2.929-2.929 2.929-7.678 0-10.606l-22.748-22.748 22.748-22.748c2.929-2.928 2.929-7.677 0-10.606z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,17 +0,0 @@
<svg width="48" height="49" viewBox="0 0 48 49" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6378_116)">
<path d="M13.3517 3.77441H4.3344C3.40346 3.80666 3.40421 5.14865 4.3344 5.18053H13.3516C14.2826 5.14828 14.2819 3.80629 13.3517 3.77441Z" fill="black"/>
<path d="M15.8219 10.6309H5.94759C4.67037 10.6309 3.63135 11.6699 3.63135 12.9471V13.9669C3.63135 15.2441 4.67037 16.2832 5.94759 16.2832H15.8219C17.0991 16.2832 18.1381 15.2441 18.1381 13.9669V12.9471C18.1381 11.67 17.0991 10.6309 15.8219 10.6309ZM16.732 13.9669C16.732 14.4688 16.3238 14.877 15.8219 14.877H5.94759C5.4457 14.877 5.03746 14.4688 5.03746 13.9669V12.9471C5.03746 12.4452 5.4457 12.037 5.94759 12.037H15.8219C16.3238 12.037 16.732 12.4452 16.732 12.9471V13.9669Z" fill="black"/>
<path d="M10.2759 20.161V23.0556C10.2759 24.3328 11.3149 25.3718 12.5921 25.3718H22.4664C23.7436 25.3718 24.7826 24.3328 24.7826 23.0556V20.161C24.7826 18.8838 23.7436 17.8447 22.4664 17.8447H12.5921C11.3149 17.8446 10.2759 18.8838 10.2759 20.161ZM23.3765 20.161V23.0556C23.3765 23.5575 22.9683 23.9657 22.4664 23.9657H12.5921C12.0902 23.9657 11.682 23.5575 11.682 23.0556V20.161C11.682 19.6591 12.0902 19.2508 12.5921 19.2508H22.4664C22.9683 19.2507 23.3765 19.6591 23.3765 20.161Z" fill="black"/>
<path d="M3.63135 32.1435C3.63135 33.4207 4.67037 34.4597 5.94759 34.4597H15.8219C17.0991 34.4597 18.1381 33.4207 18.1381 32.1435V29.2489C18.1381 27.9716 17.0991 26.9326 15.8219 26.9326H5.94759C4.67037 26.9326 3.63135 27.9716 3.63135 29.2489V32.1435ZM5.03746 29.2489C5.03746 28.747 5.4457 28.3387 5.94759 28.3387H15.8219C16.3238 28.3387 16.732 28.747 16.732 29.2489V32.1435C16.732 32.6454 16.3238 33.0536 15.8219 33.0536H5.94759C5.4457 33.0536 5.03746 32.6454 5.03746 32.1435V29.2489Z" fill="black"/>
<path d="M38.1102 19.9331C33.5406 19.9331 29.6852 23.0494 28.5583 27.2682V3.4397C28.5583 1.81976 27.2404 0.501953 25.6205 0.501953H2.93784C1.3179 0.501953 0 1.81986 0 3.4397V20.0432C0 20.4314 0.314688 20.7462 0.703056 20.7462C1.09142 20.7462 1.40611 20.4314 1.40611 20.0432V8.45258H27.1521V28.8182C23.2126 29.346 20.0003 32.203 18.9507 35.9537H5.77819C4.52478 35.9537 3.50497 36.9735 3.50497 38.227C3.50497 39.4805 4.52478 40.5002 5.77819 40.5002H18.7678C18.9094 41.2299 19.1315 41.9311 19.4245 42.5943H2.93784C2.09323 42.5943 1.40611 41.9072 1.40611 41.0626V23.6671C1.40611 23.2788 1.09142 22.964 0.703056 22.964C0.314688 22.964 0 23.2788 0 23.6671V41.0626C0 42.6825 1.3179 44.0004 2.93784 44.0004H20.1877C22.6379 47.9355 28.1585 49.6822 32.5122 47.6362L34.4133 48.4393C34.9706 48.5729 35.2962 48.357 35.39 47.7917V45.6731C37.0359 44.0629 38.0518 41.9709 38.2985 39.6981C39.9145 39.6673 41.5059 39.2395 42.9126 38.4563C43.7068 38.0141 43.0228 36.7855 42.2286 37.2278C39.8606 38.5633 36.8283 38.6501 34.3934 37.4377C34.2107 37.3484 33.9984 37.3426 33.8112 37.4218L32.5984 37.9342V36.5738C32.5984 36.3771 32.516 36.1895 32.3712 36.0564C26.7203 30.87 30.4316 21.3351 38.1102 21.3392C45.1057 21.3061 49.1324 29.5318 44.8031 35.021C44.2444 35.738 45.3535 36.6024 45.9122 35.8853C50.961 29.4838 46.2654 19.8946 38.1102 19.9331ZM1.40611 7.04647V3.4397C1.40611 2.59509 2.09323 1.90807 2.93784 1.90807H25.6205C26.4651 1.90807 27.1522 2.59519 27.1522 3.4397V7.04647H1.40611ZM5.77819 39.0941C4.62902 39.0477 4.6277 37.4068 5.77819 37.3598H18.6662C18.5935 37.9316 18.5709 38.5182 18.5987 39.0941H5.77819ZM31.1924 36.8756V38.9945C31.2862 39.5598 31.6118 39.7757 32.169 39.6421L34.0701 38.839C34.9717 39.2439 35.9161 39.5072 36.8914 39.6264C36.6543 41.6316 35.7171 43.4683 34.211 44.8537C34.0663 44.9869 33.9839 45.1745 33.9839 45.3712V46.7316L32.7711 46.2192C32.5838 46.1401 32.3715 46.1458 32.1888 46.2351C26.6793 49.009 19.9179 44.7879 19.9931 38.6138C19.993 34.0203 23.6664 30.2683 28.2304 30.14C28.3143 32.6748 29.3798 35.0983 31.1924 36.8756Z" fill="black"/>
<path d="M40.2727 27.1796C41.2036 27.1473 41.2028 25.8053 40.2727 25.7734H35.9479C35.017 25.8057 35.0177 27.1477 35.9479 27.1796H40.2727Z" fill="black"/>
<path d="M42.9383 29.1133H33.2819C32.351 29.1455 32.3517 30.4875 33.2819 30.5194H42.9383C43.8692 30.4871 43.8685 29.1452 42.9383 29.1133Z" fill="black"/>
<path d="M35.9479 32.4531C35.017 32.4854 35.0177 33.8274 35.9479 33.8592H40.2727C41.2036 33.827 41.2028 32.485 40.2727 32.4531H35.9479Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_6378_116">
<rect width="48" height="48" fill="white" transform="translate(0 0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,22 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6311_9508)">
<path d="M126.125 15.0417C127.161 15.0417 128 14.2022 128 13.1667V6.11825C128 2.74475 125.255 0 121.882 0H79.0155C75.6418 0 72.897 2.74475 72.897 6.11825V32.04C72.897 32.1707 72.8253 32.2905 72.71 32.3522L66.7345 35.552C65.5993 36.16 64.8943 37.3375 64.8943 38.625C64.8943 39.9125 65.5993 41.09 66.7345 41.698L72.71 44.8975C72.8255 44.9595 72.8973 45.079 72.8973 45.21V60.1317C72.8973 63.5055 75.642 66.25 79.0158 66.25H121.882C125.255 66.25 128 63.5052 128 60.1317V21.8333C128 20.7978 127.161 19.9583 126.125 19.9583C125.09 19.9583 124.25 20.7978 124.25 21.8333V60.1315C124.25 61.4375 123.188 62.4997 121.882 62.4997H79.0155C77.7095 62.4997 76.647 61.4373 76.647 60.1315V45.21C76.647 43.694 75.8168 42.3075 74.4803 41.5918L68.9398 38.625L74.4805 35.658C75.817 34.9422 76.6473 33.556 76.6473 32.04V6.11825C76.6473 4.81225 77.7098 3.75 79.0158 3.75H121.882C123.188 3.75 124.25 4.8125 124.25 6.11825V13.1665C124.25 14.2023 125.09 15.0417 126.125 15.0417Z" fill="black"/>
<path d="M45 49.6535V46.1367C47.5645 45.614 49.5 43.3415 49.5 40.625C49.5 37.5233 46.9767 35 43.875 35H34.875C31.7733 35 29.25 37.5233 29.25 40.625C29.25 43.3415 31.1855 45.6142 33.75 46.1367V49.6535C14.6938 52.3915 0 68.8232 0 88.625C0 100.683 5.4325 111.92 14.9047 119.453C15.7153 120.098 16.8947 119.964 17.5392 119.153C18.184 118.342 18.0495 117.163 17.239 116.519C8.6665 109.701 3.75 99.5338 3.75 88.625C3.75 68.9813 19.7313 53 39.375 53C59.0187 53 75 68.9813 75 88.625C75 108.269 59.0187 124.25 39.375 124.25C34.1278 124.25 29.0715 123.131 24.3463 120.923C23.408 120.485 22.2922 120.89 21.8538 121.828C21.4153 122.766 21.8205 123.882 22.7588 124.32C27.9848 126.762 33.5752 128 39.375 128C61.0865 128 78.75 110.337 78.75 88.625C78.75 68.8232 64.0563 52.3915 45 49.6535ZM33 40.625C33 39.591 33.841 38.75 34.875 38.75H43.875C44.909 38.75 45.75 39.591 45.75 40.625C45.75 41.659 44.909 42.5 43.875 42.5H34.875C33.841 42.5 33 41.659 33 40.625ZM37.5 49.2957V46.25H41.25V49.2957C40.6285 49.2665 40.0037 49.25 39.375 49.25C38.7463 49.25 38.1215 49.2665 37.5 49.2957Z" fill="black"/>
<path d="M71.25 88.625C71.25 71.049 56.951 56.75 39.375 56.75C21.799 56.75 7.5 71.049 7.5 88.625C7.5 106.201 21.799 120.5 39.375 120.5C56.951 120.5 71.25 106.201 71.25 88.625ZM11.25 88.625C11.25 73.1167 23.8667 60.5 39.375 60.5C54.8833 60.5 67.5 73.1167 67.5 88.625C67.5 104.133 54.8833 116.75 39.375 116.75C23.8667 116.75 11.25 104.133 11.25 88.625Z" fill="black"/>
<path d="M114.771 35.4187C116.496 33.7365 117.106 31.268 116.361 28.9762C115.616 26.6845 113.672 25.0457 111.288 24.6992L108.759 24.3317C108.149 24.243 107.622 23.86 107.349 23.3072L106.219 21.0152C105.152 18.8542 102.993 17.5117 100.583 17.5117C98.1738 17.5117 96.0145 18.8542 94.9483 21.015L93.8175 23.3067C93.5448 23.8597 93.0175 24.243 92.4073 24.3317L89.879 24.6992C87.4945 25.0457 85.5508 26.6845 84.806 28.9762C84.0613 31.268 84.6705 33.7365 86.3958 35.4187L88.226 37.2032C88.6678 37.634 88.869 38.254 88.7648 38.862L88.3325 41.3825C87.9253 43.7575 88.8835 46.1122 90.8328 47.5285C91.9343 48.3287 93.2228 48.7352 94.52 48.7352C95.519 48.7352 96.523 48.4942 97.4508 48.0062L99.7123 46.817C100.258 46.5302 100.909 46.53 101.455 46.817L103.715 48.0055C105.848 49.1267 108.384 48.944 110.334 47.5277C112.284 46.1115 113.242 43.7562 112.834 41.381L112.402 38.862C112.298 38.254 112.5 37.634 112.941 37.2035L114.771 35.4187ZM112.153 32.7337L110.323 34.5182C108.998 35.8105 108.393 37.6715 108.706 39.4957L109.138 42.0147C109.305 42.9872 108.928 43.914 108.13 44.4937C107.332 45.0737 106.334 45.1457 105.46 44.6865L103.2 43.498C101.562 42.6365 99.6053 42.6367 97.9668 43.498L95.7053 44.6872C94.8323 45.1462 93.8348 45.0742 93.0368 44.4945C92.2388 43.9147 91.862 42.9885 92.0285 42.0162L92.4608 39.496C92.7738 37.6715 92.1693 35.8107 90.8438 34.5182L89.0135 32.7337C88.307 32.045 88.0673 31.0737 88.3723 30.1352C88.6773 29.197 89.442 28.5522 90.4183 28.4102L92.9465 28.0427C94.7785 27.7765 96.3613 26.6262 97.1803 24.9662L98.311 22.6745C98.7475 21.79 99.5968 21.262 100.583 21.262C101.57 21.262 102.419 21.79 102.855 22.6745L103.986 24.9665C104.805 26.6265 106.388 27.7765 108.22 28.0427L110.748 28.4102C111.725 28.5522 112.489 29.197 112.794 30.1352C113.099 31.0735 112.86 32.045 112.153 32.7337Z" fill="black"/>
<path d="M39.375 69.328C40.4105 69.328 41.25 68.4885 41.25 67.453V66.625C41.25 65.5895 40.4105 64.75 39.375 64.75C38.3395 64.75 37.5 65.5895 37.5 66.625V67.453C37.5 68.4885 38.3395 69.328 39.375 69.328Z" fill="black"/>
<path d="M41.25 110.625V109.797C41.25 108.761 40.4105 107.922 39.375 107.922C38.3395 107.922 37.5 108.761 37.5 109.797V110.625C37.5 111.66 38.3395 112.5 39.375 112.5C40.4105 112.5 41.25 111.66 41.25 110.625Z" fill="black"/>
<path d="M55.6717 74.9795L56.2572 74.394C56.9895 73.6618 56.9895 72.4745 56.2572 71.7425C55.525 71.0103 54.3377 71.0103 53.6057 71.7425L53.0202 72.328C52.288 73.0603 52.2625 74.2738 53.0202 74.9795C53.802 75.708 55.0207 75.6248 55.6717 74.9795Z" fill="black"/>
<path d="M23.0783 102.27L22.4928 102.855C21.7605 103.588 21.7605 104.775 22.4928 105.507C23.203 106.217 24.4155 106.236 25.1443 105.507L25.7298 104.921C26.462 104.189 26.462 103.002 25.7298 102.27C24.9978 101.538 23.8105 101.538 23.0783 102.27Z" fill="black"/>
<path d="M60.5471 90.5H61.3751C62.4106 90.5 63.2501 89.6605 63.2501 88.625C63.2501 87.5895 62.4106 86.75 61.3751 86.75H60.5471C59.5116 86.75 58.6721 87.5895 58.6721 88.625C58.6721 89.6605 59.5116 90.5 60.5471 90.5Z" fill="black"/>
<path d="M18.203 86.75H17.375C16.3395 86.75 15.5 87.5895 15.5 88.625C15.5 89.6605 16.3395 90.5 17.375 90.5H18.203C19.2385 90.5 20.078 89.6605 20.078 88.625C20.078 87.5895 19.2385 86.75 18.203 86.75Z" fill="black"/>
<path d="M53.6054 105.507C54.3159 106.217 55.5276 106.236 56.2569 105.507C56.9891 104.775 56.9891 103.587 56.2569 102.855L55.6714 102.27C54.9391 101.538 53.7519 101.538 53.0199 102.27C52.2876 103.002 52.2876 104.189 53.0199 104.921L53.6054 105.507Z" fill="black"/>
<path d="M25.1445 71.7425C24.4123 71.0103 23.225 71.0103 22.493 71.7425C21.7608 72.4748 21.7608 73.662 22.493 74.394C22.493 74.394 23.0778 74.9788 23.0785 74.9795C23.784 75.685 25.0075 75.702 25.73 74.9795C26.4623 74.2473 26.4623 73.06 25.73 72.328L25.1445 71.7425Z" fill="black"/>
<path d="M42.5417 93.2714L49.2532 98.3992C49.593 98.6587 49.9932 98.7844 50.3902 98.7844C50.9545 98.7844 51.5125 98.5307 51.8815 98.0477C52.5102 97.2247 52.3527 96.0482 51.53 95.4194L44.7607 90.2474C44.9157 89.7337 45.0002 89.1892 45.0002 88.6254C45.0002 86.1809 43.4325 84.0969 41.2502 83.3229V74.9092C41.2502 73.8737 40.4107 73.0342 39.3752 73.0342C38.3397 73.0342 37.5002 73.8737 37.5002 74.9092V83.3229C35.318 84.0969 33.7502 86.1809 33.7502 88.6254C33.7502 91.7272 36.2735 94.2504 39.3752 94.2504C40.5487 94.2504 41.6392 93.8887 42.5417 93.2714ZM37.5 88.6254C37.5 87.5914 38.341 86.7504 39.375 86.7504C40.409 86.7504 41.25 87.5914 41.25 88.6254C41.25 89.6594 40.409 90.5004 39.375 90.5004C38.341 90.5004 37.5 89.6594 37.5 88.6254Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_6311_9508">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="719px" height="720px" viewBox="0 0 129.996 130.238" enable-background="new 0 0 129.996 130.238"
xml:space="preserve">
<rect fill="#00538E" width="129.996" height="130.238"/>
<path fill="#FFFFFF" d="M78.611,96.076c0,9.138-7.175,12.953-15.451,12.688c-2.938-0.095-5.799-0.838-8.916-1.855l0.707-6.578
c2.638,1.086,6.057,2.263,8.92,2.219c3.74-0.058,7.477-1.794,7.477-6.124c0-3.335-2.67-4.976-5.252-6.283
c-5.709-2.896-12.275-4.907-12.275-12.998c0-7.65,6.084-11.64,13.625-11.64c2.741,0,6.014,0.477,9.299,1.641l-0.985,6.478
c-3.577-1.403-5.14-1.901-8.078-1.901c-3.253,0-6.597,1.143-6.597,4.896c0,2.933,3.01,4.456,5.248,5.573
C72.398,85.218,78.611,87.703,78.611,96.076"/>
<path fill="#FFFFFF" d="M44.561,106.8c-2.632,0.901-6.662,1.964-13.092,1.964c-13.195,0-22.027-8.529-22.027-21.805
c0-12.882,9.375-21.455,22.086-21.455c6.036,0,8.727,0.896,12.541,2.212l-0.574,7.216c-3.51-2.294-6.498-3.211-11.674-3.211
c-9.09,0-15.158,6.578-15.115,15.412c0.043,9.506,6.648,15.416,14.822,15.416c2.437,0,4.508-0.178,5.996-0.569V91.15h-7.861v-6.103
h14.898V106.8"/>
<polyline fill="#FFFFFF" points="102.041,64.587 112.063,86.918 105.08,86.918 102.041,79.497 98.996,86.918 92.014,86.918
102.041,64.587 "/>
<polyline fill="#FFFFFF" points="112.178,87.179 121.507,108.029 114.176,108.029 107.203,91.839 112.178,87.179 "/>
<polyline fill="#FFFFFF" points="91.9,87.183 96.873,91.841 93.199,100.24 102.027,95.611 107.109,99.525 89.727,108.031
82.57,108.031 91.9,87.183 "/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,10 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M117.375 5.82422H27.1625C26.127 5.82422 25.2875 6.66347 25.2875 7.69922C25.2875 8.73497 26.127 9.57422 27.1625 9.57422H117.375C121.166 9.57422 124.25 12.6582 124.25 16.4492V29.1212H3.75V16.4492C3.75 12.6585 6.834 9.57422 10.625 9.57422H18.4125C19.448 9.57422 20.2875 8.73497 20.2875 7.69922C20.2875 6.66347 19.448 5.82422 18.4125 5.82422H10.625C4.76625 5.82422 0 10.5905 0 16.4492V107.668C0 113.527 4.76625 118.293 10.625 118.293H48.4953C53.1185 120.77 58.3983 122.176 64 122.176C69.6018 122.176 74.8815 120.77 79.5048 118.293H117.375C123.234 118.293 128 113.527 128 107.668V16.4492C128 10.5905 123.234 5.82422 117.375 5.82422ZM37.2557 77.5242H47.687C47.0868 80.5977 46.7183 83.9017 46.6203 87.3632H34.8717C35.0877 83.9435 35.8947 80.6245 37.2557 77.5242ZM73.674 116.778C76.1003 113.758 78.0847 109.612 79.4355 104.702H88.7452C85.26 110.259 79.956 114.565 73.674 116.778ZM73.3053 68.1122C74.1665 69.8347 74.909 71.7352 75.5245 73.774H65.875V60.3527C68.5888 61.2235 71.2215 63.945 73.3053 68.1122ZM76.4805 77.5242C77.1335 80.613 77.5238 83.9302 77.628 87.3632H65.875V77.5242H76.4805ZM77.6277 91.1132C77.5235 94.5462 77.1332 97.8635 76.4802 100.952H65.875V91.1132H77.6277ZM75.5242 104.702C74.9087 106.741 74.1663 108.642 73.305 110.364C71.2215 114.531 68.5887 117.253 65.8747 118.124V104.702H75.5242ZM62.125 100.952H51.5195C50.8665 97.8637 50.4763 94.5465 50.372 91.1135H62.125V100.952ZM62.125 104.702V118.124C59.4113 117.253 56.7785 114.531 54.6948 110.364C53.8335 108.642 53.0913 106.741 52.4757 104.702H62.125ZM90.733 100.952H80.313C80.9133 97.879 81.2818 94.575 81.3798 91.1135H93.128C92.9062 94.5922 92.0723 97.9077 90.733 100.952ZM81.3798 87.3632C81.2818 83.9015 80.9135 80.5977 80.313 77.5242H90.733C92.0725 80.569 92.9062 83.8842 93.128 87.3632H81.3798ZM79.4355 73.7742C78.0847 68.8642 76.1003 64.7182 73.674 61.6985C79.956 63.9117 85.26 68.2172 88.7452 73.7742H79.4355ZM62.125 60.353V73.7742H52.4757C53.0913 71.7355 53.8338 69.8347 54.6948 68.1125C56.7785 63.945 59.4113 61.2235 62.125 60.353ZM62.125 77.5242V87.3632H50.3722C50.4765 83.9302 50.8667 80.613 51.5197 77.5242H62.125ZM46.6203 91.1132C46.7183 94.575 47.0865 97.8787 47.687 100.952H37.267C35.9277 97.9075 35.0938 94.5922 34.872 91.1132H46.6203ZM48.5645 104.702C49.9152 109.612 51.8997 113.758 54.326 116.778C48.044 114.565 42.74 110.259 39.2547 104.702H48.5645ZM124.25 107.668C124.25 111.459 121.166 114.543 117.375 114.543H85.0622C92.3148 108.496 96.9375 99.397 96.9375 89.2382C96.9375 71.1897 82.282 56.3007 64 56.3007C56.5457 56.3007 49.2417 58.8607 43.4333 63.5095C42.6248 64.1565 42.494 65.3365 43.141 66.145C43.7878 66.9532 44.968 67.0847 45.7765 66.4372C48.3908 64.345 51.276 62.7467 54.347 61.6722C51.9113 64.694 49.9193 68.8497 48.5645 73.774H39.2405C39.506 73.35 39.783 72.932 40.0715 72.5197C40.6652 71.6712 40.4592 70.5022 39.6107 69.9085C38.7625 69.3142 37.5933 69.5202 36.9995 70.369C33.1152 75.917 31.0623 82.442 31.0623 89.2382C31.0623 99.397 35.6852 108.496 42.9375 114.543H10.625C6.834 114.543 3.75 111.459 3.75 107.668V48.4022H78.2968C79.3325 48.4022 80.1718 47.563 80.1718 46.5272C80.1718 45.4915 79.3325 44.6522 78.2968 44.6522H3.75V32.871H124.25V36.8865H104.122C102.787 36.8865 101.531 37.4065 100.587 38.3507L94.6523 44.2855C94.4157 44.5217 94.1017 44.652 93.768 44.652H87.2968C86.261 44.652 85.4218 45.4912 85.4218 46.527C85.4218 47.5627 86.261 48.402 87.2968 48.402H93.768C95.1032 48.402 96.3585 47.882 97.3037 46.9377L103.239 41.0027C103.475 40.7667 103.788 40.6365 104.122 40.6365H124.25V107.668Z" fill="black"/>
<path d="M120.234 19.3476C120.234 16.1728 117.651 13.5898 114.477 13.5898C111.302 13.5898 108.719 16.1728 108.719 19.3476C108.719 22.5223 111.302 25.1053 114.477 25.1053C117.651 25.1053 120.234 22.5223 120.234 19.3476ZM112.469 19.3476C112.469 18.2403 113.369 17.3398 114.476 17.3398C115.583 17.3398 116.484 18.2406 116.484 19.3476C116.484 20.4546 115.583 21.3553 114.476 21.3553C113.369 21.3553 112.469 20.4546 112.469 19.3476Z" fill="black"/>
<path d="M104.703 19.3476C104.703 16.1728 102.12 13.5898 98.9453 13.5898C95.7705 13.5898 93.1875 16.1726 93.1875 19.3476C93.1875 22.5226 95.7705 25.1053 98.9453 25.1053C102.12 25.1053 104.703 22.5223 104.703 19.3476ZM96.9375 19.3476C96.9375 18.2403 97.8383 17.3398 98.9453 17.3398C100.052 17.3398 100.953 18.2406 100.953 19.3476C100.953 20.4546 100.052 21.3553 98.9453 21.3553C97.8383 21.3553 96.9375 20.4546 96.9375 19.3476Z" fill="black"/>
<path d="M89.172 19.3476C89.172 16.1728 86.589 13.5898 83.4142 13.5898C80.2395 13.5898 77.6565 16.1728 77.6565 19.3476C77.6565 22.5223 80.2395 25.1053 83.4142 25.1053C86.589 25.1053 89.172 22.5223 89.172 19.3476ZM81.4062 19.3476C81.4062 18.2403 82.307 17.3398 83.414 17.3398C84.521 17.3398 85.4217 18.2406 85.4217 19.3476C85.4217 20.4546 84.521 21.3553 83.414 21.3553C82.307 21.3553 81.4062 20.4546 81.4062 19.3476Z" fill="black"/>
<path d="M7.76538 17.3398V21.3553C7.76538 23.4231 9.44763 25.1053 11.5154 25.1053H38.8279C40.8956 25.1053 42.5779 23.4231 42.5779 21.3553V17.3398C42.5779 15.2721 40.8956 13.5898 38.8279 13.5898H11.5154C9.44788 13.5898 7.76538 15.2721 7.76538 17.3398ZM38.8304 21.3556H11.5154V17.3398H38.8279L38.8304 21.3556Z" fill="black"/>
<path d="M93.0546 38.7617C93.0546 37.726 92.2154 36.8867 91.1796 36.8867H25.1716C24.1361 36.8867 23.2966 37.726 23.2966 38.7617C23.2966 39.7975 24.1361 40.6367 25.1716 40.6367H91.1794C92.2151 40.6367 93.0546 39.7972 93.0546 38.7617Z" fill="black"/>
<path d="M17.4062 40.6367C18.4418 40.6367 19.2812 39.7973 19.2812 38.7617C19.2812 37.7262 18.4418 36.8867 17.4062 36.8867C16.3707 36.8867 15.5312 37.7262 15.5312 38.7617C15.5312 39.7973 16.3707 40.6367 17.4062 40.6367Z" fill="black"/>
<path d="M9.64038 40.6367C10.6759 40.6367 11.5154 39.7973 11.5154 38.7617C11.5154 37.7262 10.6759 36.8867 9.64038 36.8867C8.60485 36.8867 7.76538 37.7262 7.76538 38.7617C7.76538 39.7973 8.60485 40.6367 9.64038 40.6367Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 292.1 39.47"><path fill="#fff" d="M7.44 6.89h17.2v22.06H7.44z"/><path fill="#112e51" d="M53.45 5.28h-6.16v27.31h17.87v-5.38H53.45V5.28zm30.76-.19c-8.35 0-14.67 5.89-14.67 13.85s6.32 13.93 14.67 13.93 14.67-6 14.67-13.93S92.56 5.09 84.21 5.09zm.08 22.39a8.49 8.49 0 0 1-8.43-8.54 8.4 8.4 0 0 1 8.43-8.47 8.29 8.29 0 0 1 8.27 8.47 8.36 8.36 0 0 1-8.27 8.54zm35.36-17.05a11.43 11.43 0 0 1 7.49 3l3.47-4.41a17.21 17.21 0 0 0-11-4c-8.51 0-14.9 6-14.9 13.85s6.29 13.99 14.58 13.99a20.21 20.21 0 0 0 11.08-3.55V17.57h-10.11v4.82H125V26a12.42 12.42 0 0 1-5.34 1.48 8.52 8.52 0 1 1 0-17zm19.61-5.15h6.17v27.31h-6.17zm34.53 17.56L160.52 5.28h-5.61v27.31h5.89V15.07l13.22 17.52h5.62V5.28h-5.85v17.56zm17.47 2.85a3.39 3.39 0 0 0-3.47 3.56 3.47 3.47 0 1 0 6.94 0 3.36 3.36 0 0 0-3.47-3.56zm23.15-15.26a11.43 11.43 0 0 1 7.49 3l3.47-4.41a17.21 17.21 0 0 0-11-4c-8.51 0-14.9 6-14.9 13.85s6.28 13.93 14.55 13.93a20.21 20.21 0 0 0 11.08-3.55V17.57H215v4.82h4.77V26a12.42 12.42 0 0 1-5.34 1.48 8.52 8.52 0 1 1 0-17zm32.23-5.34C238.29 5.09 232 11 232 18.94s6.32 13.93 14.67 13.93 14.67-6 14.67-13.93S255 5.09 246.64 5.09zm.08 22.39a8.49 8.49 0 0 1-8.43-8.54 8.4 8.4 0 0 1 8.43-8.47 8.29 8.29 0 0 1 8.28 8.47 8.36 8.36 0 0 1-8.28 8.54zm39.02-22.2-7.49 20.6-7.45-20.6h-6.63l10.73 27.31h6.36L292.1 5.28h-6.36z"/><path fill="#e21d3e" d="m11.38 25.34 1.45-8.28a4.39 4.39 0 0 1 2.71-7.86V0H4.34A4.34 4.34 0 0 0 0 4.34v21.28a2.62 2.62 0 0 0 .26 1.16c.94 1.9 4.65 8 15.27 12.69V25.7a24.3 24.3 0 0 1-4.15-.36z"/><path fill="#b51e23" d="M26.72 0H15.53v9.19A4.39 4.39 0 0 1 18.28 17l1.45 8.32a24.29 24.29 0 0 1-4.2.36v13.79C26.14 34.8 29.86 28.68 30.8 26.79a2.62 2.62 0 0 0 .26-1.16V4.34A4.34 4.34 0 0 0 26.72 0z"/></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,17 +0,0 @@
<svg width="305" height="304" viewBox="0 0 381 380" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M163.41 47.1289H32.7852C14.983 47.1289 0.5 61.6119 0.5 79.414V293.793C0.755313 301.164 11.3797 301.158 11.6328 293.793V79.414C11.6328 67.7506 21.1217 58.2617 32.7852 58.2617H163.41C175.074 58.2617 184.562 67.7506 184.562 79.414V335.84H11.6328V319.851C11.3775 312.481 0.753086 312.487 0.5 319.851V346.601C0.5 364.403 14.983 378.887 32.7852 378.887H163.41C181.212 378.887 195.695 364.403 195.695 346.601V79.414C195.695 61.6119 181.212 47.1289 163.41 47.1289ZM163.41 367.754H32.7852C21.2464 367.754 11.8414 358.464 11.6425 346.972H184.554C184.354 358.464 174.949 367.754 163.41 367.754Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M72.8629 102.793H123.332C145.461 101.951 145.444 70.2287 123.332 69.3945H72.8629C50.7338 70.2362 50.7509 101.959 72.8629 102.793ZM72.8629 80.5273H123.332C130.701 80.7893 130.696 91.4004 123.332 91.6601H72.8629C65.4937 91.3981 65.4989 80.7871 72.8629 80.5273Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M41.6899 265.26V303.853C41.6899 308.56 45.5189 312.389 50.2251 312.389H145.225C149.931 312.389 153.76 308.56 153.76 303.853V265.26C153.76 260.554 149.931 256.725 145.225 256.725H50.2251C45.5181 256.725 41.6899 260.554 41.6899 265.26ZM52.8228 267.857H142.627V301.256H52.8228V267.857Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M374.934 50.0976C378.008 50.0976 380.5 47.6054 380.5 44.5312V21.4203C380.5 9.60909 370.835 0 358.955 0H258.626C246.746 0 237.081 9.60909 237.081 21.4203V99.174L211.672 116.104C207.446 118.746 207.447 125.434 211.673 128.076L237.081 145.005V195.298C237.081 207.109 246.746 216.718 258.626 216.718H358.955C370.835 216.719 380.5 207.109 380.5 195.298V71.9098C380.245 64.5391 369.62 64.545 369.367 71.9098V195.298C369.367 200.971 364.697 205.586 358.955 205.586H258.626C252.885 205.586 248.214 200.971 248.214 195.298V143.773C248.214 140.812 246.74 138.063 244.272 136.418L222.766 122.09L244.272 107.761C246.741 106.116 248.214 103.367 248.214 100.407V21.4203C248.214 15.7477 252.885 11.1328 258.626 11.1328H358.955C364.697 11.1328 369.367 15.7477 369.367 21.4203V44.5312C369.367 47.6054 371.86 50.0976 374.934 50.0976Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M279.025 44.4072H313.172C320.542 44.1519 320.537 33.5275 313.172 33.2744H279.025C271.655 33.5305 271.66 44.1549 279.025 44.4072Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M279.025 116.647H313.172C320.542 116.392 320.537 105.768 313.172 105.515H279.025C271.655 105.77 271.66 116.394 279.025 116.647Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M279.025 62.4678H336.689C344.059 62.2125 344.054 51.588 336.689 51.335H279.025C271.655 51.5903 271.66 62.2147 279.025 62.4678Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M279.025 80.5273H336.689C344.059 80.272 344.054 69.6476 336.689 69.3945H279.025C271.655 69.6498 271.66 80.2743 279.025 80.5273Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M98.0967 205.319C108.123 205.319 116.28 197.161 116.28 187.135C115.366 163.045 80.8237 163.052 79.9131 187.135C79.9131 197.161 88.0705 205.319 98.0967 205.319ZM98.0967 180.084C101.984 180.084 105.147 183.248 105.147 187.135C104.793 196.476 91.3992 196.473 91.0459 187.135C91.0459 183.247 94.2091 180.084 98.0967 180.084Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M49.0186 181.389C49.7927 183.69 51.1932 185.667 53.0569 187.135C51.1932 188.603 49.7935 190.58 49.0186 192.88C47.9662 196.005 48.1948 199.354 49.6621 202.308L56.7158 216.509C59.7425 222.603 67.1644 225.101 73.2592 222.078L77.814 219.82V224.885C77.814 231.693 83.3529 237.233 90.1618 237.233H106.039C112.844 237.233 118.38 231.697 118.38 224.891V219.82L122.935 222.078C129.032 225.101 136.452 222.603 139.479 216.509L146.533 202.305C148 199.352 148.229 196.004 147.177 192.879C146.403 190.578 145.001 188.601 143.138 187.134C147.636 183.694 149.157 177.013 146.532 171.96L139.48 157.763C136.453 151.668 129.029 149.168 122.932 152.192L118.38 154.449V149.378C118.38 142.573 112.844 137.036 106.039 137.036H90.1618C83.3537 137.036 77.814 142.575 77.814 149.384V154.449L73.2622 152.192C67.1651 149.168 59.7417 151.667 56.7144 157.762L49.6621 171.96C48.1948 174.916 47.9662 178.264 49.0186 181.389ZM59.6319 176.914L66.6842 162.717C66.9825 162.115 67.7151 161.867 68.3162 162.167L78.764 167.347C80.9646 168.437 83.5259 168.316 85.6129 167.021C87.7007 165.726 88.9468 163.486 88.9468 161.03V149.385C88.9468 148.715 89.4916 148.17 90.1618 148.17H106.039C106.705 148.17 107.247 148.712 107.247 149.379V161.03C107.247 163.486 108.493 165.726 110.581 167.021C112.668 168.316 115.228 168.439 117.43 167.346L127.878 162.166C128.48 161.868 129.211 162.115 129.51 162.716L136.563 176.913C136.747 177.284 136.698 177.62 136.626 177.836C136.553 178.053 136.388 178.349 136.016 178.533L131.411 180.817C126.23 183.267 126.231 191.002 131.411 193.451L136.019 195.736C136.39 195.92 136.554 196.215 136.626 196.431C136.699 196.647 136.747 196.982 136.563 197.351L129.509 211.555C129.211 212.154 128.48 212.401 127.881 212.103L117.43 206.921C115.229 205.83 112.668 205.952 110.582 207.247C108.494 208.542 107.248 210.782 107.248 213.239V224.89C107.248 225.556 106.706 226.099 106.039 226.099H90.1625C89.4931 226.099 88.9476 225.554 88.9476 224.884V213.239C88.9476 210.782 87.7014 208.542 85.6137 207.247C83.5229 205.95 80.9601 205.832 78.764 206.922L68.3133 212.104C67.7121 212.401 66.9833 212.155 66.6864 211.556L59.6326 197.356C59.4486 196.985 59.4968 196.649 59.5696 196.432C59.6423 196.215 59.807 195.919 60.1781 195.735L64.7842 193.451C69.9654 191.001 69.9639 183.266 64.7842 180.818L60.1789 178.534C59.8078 178.35 59.643 178.053 59.5703 177.837C59.4968 177.62 59.4486 177.285 59.6319 176.914Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M363.426 257.783H235.796C226.382 257.783 218.723 265.442 218.723 274.856V362.927C218.723 372.341 226.383 380 235.796 380H363.426C372.841 380 380.499 372.341 380.499 362.927V274.856C380.499 265.442 372.841 257.783 363.426 257.783ZM369.366 362.927C369.366 366.203 366.702 368.867 363.426 368.867H235.796C232.52 368.867 229.856 366.203 229.856 362.927V274.856C229.856 271.58 232.521 268.916 235.796 268.916H363.426C366.702 268.916 369.366 271.58 369.366 274.856V362.927Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M268.272 141.779V181.381C268.272 185.809 271.875 189.412 276.304 189.412H340.926C345.354 189.412 348.956 185.809 348.956 181.381V141.779C348.956 137.351 345.354 133.749 340.926 133.749H276.304C271.875 133.748 268.272 137.35 268.272 141.779ZM279.405 144.881H337.824V178.279H279.405V144.881Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M273.174 297.591C271.206 295.229 267.695 294.909 265.334 296.878L241.975 316.344C239.33 318.441 239.331 322.8 241.975 324.897L265.334 344.364C266.375 345.23 267.638 345.654 268.895 345.653C270.489 345.653 272.073 344.971 273.174 343.65C275.142 341.289 274.823 337.779 272.462 335.811L254.233 320.62L272.462 305.431C274.823 303.462 275.142 299.953 273.174 297.591Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M357.249 316.344L333.889 296.878C331.527 294.91 328.018 295.229 326.049 297.59C324.081 299.952 324.4 303.462 326.762 305.43L344.99 320.62L326.762 335.81C324.4 337.778 324.081 341.288 326.049 343.65C328.026 346.021 331.541 346.322 333.889 344.362L357.249 324.895C359.893 322.799 359.892 318.441 357.249 316.344Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
<path d="M314.73 284.336C311.892 283.154 308.634 284.495 307.451 287.333L281.496 349.626C280.314 352.464 281.656 355.723 284.493 356.905C287.26 358.079 290.59 356.775 291.772 353.908L317.727 291.616C318.91 288.778 317.569 285.519 314.73 284.336Z" fill="#005EA2" stroke="#DFE1E2" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,7 +0,0 @@
<svg width="187" height="230" viewBox="0 0 187 230" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M89.1324 230V203.125H57.936C60.0752 226.499 79.7735 230 89.1324 230Z" fill="#2491FF"/>
<path d="M89.1324 230V203.125H121.22C119.02 226.499 98.7587 230 89.1324 230Z" fill="#005EA2"/>
<path d="M71.4869 19.5185C71.4869 3.35574 82.3456 0 89.1324 0V194.166H0V174.861L22.1703 153.759V87.3122C22.1703 47.8033 53.8415 24.1578 71.4869 19.5185Z" fill="#2491FF"/>
<path d="M89.1324 0C95.9192 0 106.778 3.35574 106.778 19.5185C112.193 20.9422 118.928 24.1557 125.664 29.0681C118.405 36.2889 113.911 46.2873 113.911 57.3353C113.911 79.3543 131.761 97.2043 153.78 97.2043C154.557 97.2043 155.293 97.2481 156.058 97.2043L156.094 153.759L178.265 174.861V194.166H89.1324V0Z" fill="#005EA2"/>
<path d="M186.435 57.1535C186.435 74.9784 171.985 89.4283 154.16 89.4283C136.335 89.4283 121.885 74.9784 121.885 57.1535C121.885 39.3286 136.335 24.8786 154.16 24.8786C171.985 24.8786 186.435 39.3286 186.435 57.1535Z" fill="#D54309"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,17 +0,0 @@
<svg width="1596" height="400" viewBox="0 0 1596 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M155.013 400V353.26H100.758C104.479 393.912 138.736 400 155.013 400Z" fill="#58B4FF"/>
<path d="M155.013 400V353.26H210.818C206.991 393.912 171.754 400 155.013 400Z" fill="#0076D6"/>
<path d="M124.325 33.9452C124.325 5.83608 143.21 0 155.013 0V337.68H0V304.105L38.5571 267.407V151.847C38.5571 83.1361 93.6374 42.0136 124.325 33.9452Z" fill="#58B4FF"/>
<path d="M155.013 0C166.816 0 185.701 5.83608 185.701 33.9452C195.118 36.4212 206.832 42.0098 218.547 50.5533C205.922 63.1111 198.106 80.4997 198.106 99.7136C198.106 138.008 229.15 169.051 267.444 169.051C268.795 169.051 270.074 169.127 271.406 169.051L271.469 267.407L310.026 304.105V337.68H155.013V0Z" fill="#0076D6"/>
<path d="M324.234 99.3973C324.234 130.397 299.104 155.528 268.104 155.528C237.104 155.528 211.974 130.397 211.974 99.3973C211.974 68.3975 237.104 43.2672 268.104 43.2672C299.104 43.2672 324.234 68.3975 324.234 99.3973Z" fill="#D54309"/>
<path d="M416.684 290.469C414.747 290.469 413.25 289.94 412.194 288.884C411.137 287.651 410.609 286.154 410.609 284.393V111.909C410.609 109.972 411.137 108.475 412.194 107.418C413.25 106.186 414.747 105.569 416.684 105.569H428.835C430.948 105.569 432.532 106.098 433.589 107.154C434.646 108.035 435.262 108.739 435.438 109.267L523.133 243.98V111.909C523.133 109.972 523.662 108.475 524.718 107.418C525.775 106.186 527.271 105.569 529.208 105.569H541.887C543.824 105.569 545.321 106.186 546.378 107.418C547.61 108.475 548.227 109.972 548.227 111.909V284.129C548.227 285.89 547.61 287.387 546.378 288.62C545.321 289.852 543.912 290.469 542.151 290.469H529.473C527.536 290.469 526.039 289.94 524.982 288.884C524.102 287.827 523.485 287.123 523.133 286.771L435.702 153.379V284.393C435.702 286.154 435.086 287.651 433.853 288.884C432.797 289.94 431.3 290.469 429.363 290.469H416.684Z" fill="#EDEFF0"/>
<path d="M636.275 293.11C622.892 293.11 611.71 290.557 602.729 285.45C593.748 280.343 586.88 273.299 582.126 264.319C577.371 255.162 574.73 244.772 574.201 233.15C574.025 230.156 573.937 226.37 573.937 221.792C573.937 217.037 574.025 213.251 574.201 210.434C574.73 198.635 577.371 188.246 582.126 179.265C587.056 170.284 594.012 163.24 602.993 158.133C611.974 153.027 623.068 150.473 636.275 150.473C649.482 150.473 660.576 153.027 669.557 158.133C678.537 163.24 685.405 170.284 690.16 179.265C695.09 188.246 697.82 198.635 698.348 210.434C698.524 213.251 698.612 217.037 698.612 221.792C698.612 226.37 698.524 230.156 698.348 233.15C697.82 244.772 695.178 255.162 690.424 264.319C685.669 273.299 678.802 280.343 669.821 285.45C660.84 290.557 649.658 293.11 636.275 293.11ZM636.275 272.771C647.193 272.771 655.909 269.337 662.425 262.47C668.94 255.426 672.462 245.212 672.99 231.829C673.167 229.188 673.255 225.842 673.255 221.792C673.255 217.742 673.167 214.396 672.99 211.754C672.462 198.371 668.94 188.246 662.425 181.378C655.909 174.334 647.193 170.812 636.275 170.812C625.357 170.812 616.552 174.334 609.861 181.378C603.345 188.246 599.911 198.371 599.559 211.754C599.383 214.396 599.295 217.742 599.295 221.792C599.295 225.842 599.383 229.188 599.559 231.829C599.911 245.212 603.345 255.426 609.861 262.47C616.552 269.337 625.357 272.771 636.275 272.771Z" fill="#EDEFF0"/>
<path d="M781.377 290.469C771.516 290.469 763.504 288.62 757.341 284.922C751.177 281.048 746.687 275.677 743.869 268.809C741.052 261.765 739.643 253.489 739.643 243.98V174.246H719.04C717.279 174.246 715.782 173.718 714.549 172.661C713.493 171.429 712.965 169.932 712.965 168.171V159.19C712.965 157.429 713.493 156.02 714.549 154.964C715.782 153.731 717.279 153.115 719.04 153.115H739.643V109.003C739.643 107.242 740.171 105.833 741.228 104.777C742.461 103.544 743.957 102.928 745.718 102.928H758.133C759.894 102.928 761.303 103.544 762.359 104.777C763.592 105.833 764.208 107.242 764.208 109.003V153.115H796.962C798.723 153.115 800.132 153.731 801.188 154.964C802.421 156.02 803.037 157.429 803.037 159.19V168.171C803.037 169.932 802.421 171.429 801.188 172.661C800.132 173.718 798.723 174.246 796.962 174.246H764.208V242.131C764.208 250.407 765.617 256.923 768.434 261.677C771.252 266.432 776.271 268.809 783.491 268.809H799.603C801.364 268.809 802.773 269.425 803.83 270.658C805.062 271.715 805.678 273.123 805.678 274.884V284.393C805.678 286.154 805.062 287.651 803.83 288.884C802.773 289.94 801.364 290.469 799.603 290.469H781.377Z" fill="#EDEFF0"/>
<path d="M834.371 290.469C832.61 290.469 831.113 289.94 829.88 288.884C828.824 287.651 828.296 286.154 828.296 284.393V159.19C828.296 157.429 828.824 156.02 829.88 154.964C831.113 153.731 832.61 153.115 834.371 153.115H847.05C848.811 153.115 850.219 153.731 851.276 154.964C852.333 156.02 852.861 157.429 852.861 159.19V284.393C852.861 286.154 852.333 287.651 851.276 288.884C850.219 289.94 848.811 290.469 847.05 290.469H834.371ZM832.258 127.493C830.497 127.493 829 126.965 827.767 125.908C826.711 124.675 826.182 123.179 826.182 121.418V107.154C826.182 105.393 826.711 103.984 827.767 102.928C829 101.695 830.497 101.079 832.258 101.079H848.899C850.66 101.079 852.068 101.695 853.125 102.928C854.358 103.984 854.974 105.393 854.974 107.154V121.418C854.974 123.179 854.358 124.675 853.125 125.908C852.068 126.965 850.66 127.493 848.899 127.493H832.258Z" fill="#EDEFF0"/>
<path d="M912.078 290.469C910.317 290.469 908.82 289.94 907.588 288.884C906.531 287.651 906.003 286.154 906.003 284.393V174.246H883.551C881.79 174.246 880.293 173.718 879.06 172.661C878.004 171.429 877.476 169.932 877.476 168.171V159.19C877.476 157.429 878.004 156.02 879.06 154.964C880.293 153.731 881.79 153.115 883.551 153.115H906.003V139.908C906.003 130.927 907.5 123.091 910.493 116.399C913.487 109.531 918.242 104.249 924.757 100.551C931.449 96.8525 940.341 95.0036 951.435 95.0036H967.284C969.045 95.0036 970.454 95.6199 971.51 96.8525C972.567 97.9091 973.095 99.3179 973.095 101.079V110.06C973.095 111.821 972.567 113.317 971.51 114.55C970.454 115.607 969.045 116.135 967.284 116.135H951.964C943.687 116.135 938.052 118.336 935.059 122.738C932.065 126.965 930.568 133.128 930.568 141.228V153.115H964.642C966.403 153.115 967.812 153.731 968.869 154.964C969.925 156.02 970.454 157.429 970.454 159.19V168.171C970.454 169.932 969.925 171.429 968.869 172.661C967.812 173.718 966.403 174.246 964.642 174.246H930.568V284.393C930.568 286.154 929.952 287.651 928.719 288.884C927.663 289.94 926.254 290.469 924.493 290.469H912.078Z" fill="#EDEFF0"/>
<path d="M1017.94 340.656C1016.53 340.656 1015.3 340.127 1014.24 339.071C1013.18 338.014 1012.65 336.782 1012.65 335.373C1012.65 334.668 1012.74 333.964 1012.92 333.26C1013.09 332.555 1013.45 331.675 1013.98 330.618L1034.31 282.28L983.863 163.152C982.982 161.039 982.542 159.542 982.542 158.662C982.542 157.077 983.071 155.756 984.127 154.7C985.184 153.643 986.504 153.115 988.089 153.115H1001.03C1002.79 153.115 1004.2 153.555 1005.26 154.435C1006.31 155.316 1007.02 156.373 1007.37 157.605L1047.52 253.753L1088.73 157.605C1089.26 156.373 1089.96 155.316 1090.84 154.435C1091.9 153.555 1093.39 153.115 1095.33 153.115H1107.75C1109.33 153.115 1110.65 153.643 1111.71 154.7C1112.76 155.756 1113.29 156.989 1113.29 158.398C1113.29 159.278 1112.85 160.863 1111.97 163.152L1036.96 336.165C1036.43 337.398 1035.63 338.454 1034.58 339.335C1033.7 340.215 1032.29 340.656 1030.35 340.656H1017.94Z" fill="#EDEFF0"/>
<path d="M1128.65 290.469C1126.89 290.469 1125.39 289.94 1124.16 288.884C1123.1 287.651 1122.57 286.154 1122.57 284.393V265.375C1122.57 263.614 1123.1 262.205 1124.16 261.149C1125.39 259.916 1126.89 259.3 1128.65 259.3H1147.67C1149.43 259.3 1150.84 259.916 1151.89 261.149C1153.13 262.205 1153.74 263.614 1153.74 265.375V284.393C1153.74 286.154 1153.13 287.651 1151.89 288.884C1150.84 289.94 1149.43 290.469 1147.67 290.469H1128.65Z" fill="#EDEFF0"/>
<path d="M1244.66 348.58C1232.68 348.58 1222.82 346.995 1215.07 343.825C1207.5 340.656 1201.51 336.782 1197.11 332.203C1192.89 327.801 1189.8 323.31 1187.87 318.732C1186.11 314.329 1185.14 310.808 1184.96 308.166C1184.79 306.405 1185.31 304.908 1186.55 303.676C1187.96 302.443 1189.45 301.827 1191.04 301.827H1203.72C1205.3 301.827 1206.62 302.179 1207.68 302.883C1208.73 303.588 1209.61 305.084 1210.32 307.374C1211.38 310.015 1212.96 312.921 1215.07 316.09C1217.19 319.436 1220.44 322.254 1224.85 324.543C1229.25 327.008 1235.41 328.241 1243.34 328.241C1251.61 328.241 1258.57 327.096 1264.2 324.807C1269.84 322.694 1274.07 318.82 1276.88 313.185C1279.88 307.726 1281.37 300.154 1281.37 290.469V272.507C1277.15 277.966 1271.6 282.544 1264.73 286.242C1257.86 289.764 1249.24 291.525 1238.85 291.525C1228.99 291.525 1220.53 289.852 1213.49 286.507C1206.45 282.985 1200.63 278.318 1196.06 272.507C1191.65 266.52 1188.31 259.74 1186.02 252.168C1183.91 244.596 1182.67 236.672 1182.32 228.395C1182.14 223.465 1182.14 218.622 1182.32 213.867C1182.67 205.591 1183.91 197.667 1186.02 190.095C1188.31 182.523 1191.65 175.743 1196.06 169.756C1200.63 163.769 1206.45 159.102 1213.49 155.756C1220.53 152.234 1228.99 150.473 1238.85 150.473C1249.41 150.473 1258.13 152.498 1265 156.549C1272.04 160.423 1277.76 165.177 1282.17 170.812V159.454C1282.17 157.693 1282.69 156.196 1283.75 154.964C1284.98 153.731 1286.48 153.115 1288.24 153.115H1300.39C1302.15 153.115 1303.65 153.731 1304.88 154.964C1306.11 156.196 1306.73 157.693 1306.73 159.454V287.563C1306.73 299.361 1304.71 309.839 1300.66 318.996C1296.78 328.153 1290.27 335.373 1281.11 340.656C1272.13 345.938 1259.98 348.58 1244.66 348.58ZM1244.13 270.394C1252.93 270.394 1259.98 268.369 1265.26 264.319C1270.72 260.268 1274.68 255.162 1277.15 248.998C1279.79 242.835 1281.2 236.584 1281.37 230.244C1281.55 227.779 1281.64 224.785 1281.64 221.263C1281.64 217.565 1281.55 214.484 1281.37 212.018C1281.2 205.679 1279.79 199.428 1277.15 193.264C1274.68 187.101 1270.72 181.994 1265.26 177.944C1259.98 173.894 1252.93 171.869 1244.13 171.869C1235.32 171.869 1228.28 173.894 1223 177.944C1217.72 181.818 1213.93 187.013 1211.64 193.529C1209.35 199.868 1208.03 206.736 1207.68 214.132C1207.5 218.71 1207.5 223.377 1207.68 228.131C1208.03 235.527 1209.35 242.483 1211.64 248.998C1213.93 255.338 1217.72 260.533 1223 264.583C1228.28 268.457 1235.32 270.394 1244.13 270.394Z" fill="#EDEFF0"/>
<path d="M1394.34 293.11C1380.96 293.11 1369.78 290.557 1360.8 285.45C1351.81 280.343 1344.95 273.299 1340.19 264.319C1335.44 255.162 1332.8 244.772 1332.27 233.15C1332.09 230.156 1332 226.37 1332 221.792C1332 217.037 1332.09 213.251 1332.27 210.434C1332.8 198.635 1335.44 188.246 1340.19 179.265C1345.12 170.284 1352.08 163.24 1361.06 158.133C1370.04 153.027 1381.13 150.473 1394.34 150.473C1407.55 150.473 1418.64 153.027 1427.62 158.133C1436.6 163.24 1443.47 170.284 1448.23 179.265C1453.16 188.246 1455.89 198.635 1456.42 210.434C1456.59 213.251 1456.68 217.037 1456.68 221.792C1456.68 226.37 1456.59 230.156 1456.42 233.15C1455.89 244.772 1453.25 255.162 1448.49 264.319C1443.74 273.299 1436.87 280.343 1427.89 285.45C1418.91 290.557 1407.72 293.11 1394.34 293.11ZM1394.34 272.771C1405.26 272.771 1413.98 269.337 1420.49 262.47C1427.01 255.426 1430.53 245.212 1431.06 231.829C1431.23 229.188 1431.32 225.842 1431.32 221.792C1431.32 217.742 1431.23 214.396 1431.06 211.754C1430.53 198.371 1427.01 188.246 1420.49 181.378C1413.98 174.334 1405.26 170.812 1394.34 170.812C1383.42 170.812 1374.62 174.334 1367.93 181.378C1361.41 188.246 1357.98 198.371 1357.63 211.754C1357.45 214.396 1357.36 217.742 1357.36 221.792C1357.36 225.842 1357.45 229.188 1357.63 231.829C1357.98 245.212 1361.41 255.426 1367.93 262.47C1374.62 269.337 1383.42 272.771 1394.34 272.771Z" fill="#EDEFF0"/>
<path d="M1524.74 290.469C1522.1 290.469 1519.98 289.852 1518.4 288.62C1516.99 287.387 1515.85 285.802 1514.97 283.865L1466.1 161.303C1465.75 160.423 1465.57 159.542 1465.57 158.662C1465.57 157.077 1466.1 155.756 1467.16 154.7C1468.21 153.643 1469.53 153.115 1471.12 153.115H1483.8C1485.73 153.115 1487.23 153.643 1488.29 154.7C1489.34 155.756 1489.96 156.725 1490.14 157.605L1530.55 261.677L1570.7 157.605C1571.05 156.725 1571.67 155.756 1572.55 154.7C1573.61 153.643 1575.1 153.115 1577.04 153.115H1589.98C1591.39 153.115 1592.62 153.643 1593.68 154.7C1594.91 155.756 1595.53 157.077 1595.53 158.662C1595.53 159.542 1595.35 160.423 1595 161.303L1545.87 283.865C1545.17 285.802 1544.02 287.387 1542.44 288.62C1541.03 289.852 1538.91 290.469 1536.1 290.469H1524.74Z" fill="#EDEFF0"/>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,17 +0,0 @@
<svg width="1596" height="400" viewBox="0 0 1596 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M155.013 400V353.26H100.759C104.479 393.912 138.737 400 155.013 400Z" fill="#2491FF"/>
<path d="M155.013 400V353.26H210.818C206.992 393.912 171.755 400 155.013 400Z" fill="#005EA2"/>
<path d="M124.326 33.9452C124.326 5.83608 143.21 0 155.014 0V337.68H0V304.105L38.5573 267.407V151.847C38.5573 83.1361 93.6378 42.0136 124.326 33.9452Z" fill="#2491FF"/>
<path d="M155.014 0C166.817 0 185.702 5.83608 185.702 33.9452C195.119 36.4212 206.833 42.0098 218.548 50.5533C205.923 63.1111 198.107 80.4997 198.107 99.7136C198.107 138.008 229.151 169.051 267.445 169.051C268.796 169.051 270.075 169.127 271.407 169.051L271.47 267.407L310.027 304.105V337.68H155.014V0Z" fill="#005EA2"/>
<path d="M324.236 99.3973C324.236 130.397 299.105 155.528 268.105 155.528C237.105 155.528 211.975 130.397 211.975 99.3973C211.975 68.3975 237.105 43.2672 268.105 43.2672C299.105 43.2672 324.236 68.3975 324.236 99.3973Z" fill="#D54309"/>
<path d="M416.686 290.469C414.749 290.469 413.252 289.94 412.195 288.884C411.139 287.651 410.61 286.154 410.61 284.393V111.909C410.61 109.972 411.139 108.475 412.195 107.418C413.252 106.186 414.749 105.569 416.686 105.569H428.836C430.95 105.569 432.534 106.098 433.591 107.154C434.647 108.035 435.264 108.739 435.44 109.267L523.135 243.98V111.909C523.135 109.972 523.664 108.475 524.72 107.418C525.777 106.186 527.274 105.569 529.211 105.569H541.89C543.827 105.569 545.323 106.186 546.38 107.418C547.613 108.475 548.229 109.972 548.229 111.909V284.129C548.229 285.89 547.613 287.387 546.38 288.62C545.323 289.852 543.915 290.469 542.154 290.469H529.475C527.538 290.469 526.041 289.94 524.984 288.884C524.104 287.827 523.488 287.123 523.135 286.771L435.704 153.379V284.393C435.704 286.154 435.088 287.651 433.855 288.884C432.798 289.94 431.302 290.469 429.365 290.469H416.686Z" fill="#2D2E2F"/>
<path d="M636.277 293.11C622.894 293.11 611.712 290.557 602.731 285.45C593.75 280.343 586.883 273.299 582.128 264.319C577.373 255.162 574.732 244.772 574.204 233.15C574.028 230.156 573.94 226.37 573.94 221.792C573.94 217.037 574.028 213.251 574.204 210.434C574.732 198.635 577.373 188.246 582.128 179.265C587.059 170.284 594.014 163.24 602.995 158.133C611.976 153.027 623.07 150.473 636.277 150.473C649.485 150.473 660.579 153.027 669.559 158.133C678.54 163.24 685.408 170.284 690.163 179.265C695.093 188.246 697.823 198.635 698.351 210.434C698.527 213.251 698.615 217.037 698.615 221.792C698.615 226.37 698.527 230.156 698.351 233.15C697.823 244.772 695.181 255.162 690.427 264.319C685.672 273.299 678.804 280.343 669.824 285.45C660.843 290.557 649.661 293.11 636.277 293.11ZM636.277 272.771C647.195 272.771 655.912 269.337 662.428 262.47C668.943 255.426 672.465 245.212 672.993 231.829C673.169 229.188 673.257 225.842 673.257 221.792C673.257 217.742 673.169 214.396 672.993 211.754C672.465 198.371 668.943 188.246 662.428 181.378C655.912 174.334 647.195 170.812 636.277 170.812C625.359 170.812 616.555 174.334 609.863 181.378C603.348 188.246 599.914 198.371 599.562 211.754C599.385 214.396 599.297 217.742 599.297 221.792C599.297 225.842 599.385 229.188 599.562 231.829C599.914 245.212 603.348 255.426 609.863 262.47C616.555 269.337 625.359 272.771 636.277 272.771Z" fill="#2D2E2F"/>
<path d="M781.381 290.469C771.519 290.469 763.507 288.62 757.344 284.922C751.18 281.048 746.69 275.677 743.872 268.809C741.055 261.765 739.646 253.489 739.646 243.98V174.246H719.043C717.282 174.246 715.785 173.718 714.552 172.661C713.496 171.429 712.968 169.932 712.968 168.171V159.19C712.968 157.429 713.496 156.02 714.552 154.964C715.785 153.731 717.282 153.115 719.043 153.115H739.646V109.003C739.646 107.242 740.174 105.833 741.231 104.777C742.464 103.544 743.96 102.928 745.721 102.928H758.136C759.897 102.928 761.306 103.544 762.362 104.777C763.595 105.833 764.211 107.242 764.211 109.003V153.115H796.965C798.726 153.115 800.135 153.731 801.191 154.964C802.424 156.02 803.04 157.429 803.04 159.19V168.171C803.04 169.932 802.424 171.429 801.191 172.661C800.135 173.718 798.726 174.246 796.965 174.246H764.211V242.131C764.211 250.407 765.62 256.923 768.438 261.677C771.255 266.432 776.274 268.809 783.494 268.809H799.607C801.368 268.809 802.776 269.425 803.833 270.658C805.066 271.715 805.682 273.123 805.682 274.884V284.393C805.682 286.154 805.066 287.651 803.833 288.884C802.776 289.94 801.368 290.469 799.607 290.469H781.381Z" fill="#2D2E2F"/>
<path d="M834.374 290.469C832.613 290.469 831.117 289.94 829.884 288.884C828.827 287.651 828.299 286.154 828.299 284.393V159.19C828.299 157.429 828.827 156.02 829.884 154.964C831.117 153.731 832.613 153.115 834.374 153.115H847.053C848.814 153.115 850.223 153.731 851.28 154.964C852.336 156.02 852.864 157.429 852.864 159.19V284.393C852.864 286.154 852.336 287.651 851.28 288.884C850.223 289.94 848.814 290.469 847.053 290.469H834.374ZM832.261 127.493C830.5 127.493 829.004 126.965 827.771 125.908C826.714 124.675 826.186 123.179 826.186 121.418V107.154C826.186 105.393 826.714 103.984 827.771 102.928C829.004 101.695 830.5 101.079 832.261 101.079H848.902C850.663 101.079 852.072 101.695 853.129 102.928C854.361 103.984 854.978 105.393 854.978 107.154V121.418C854.978 123.179 854.361 124.675 853.129 125.908C852.072 126.965 850.663 127.493 848.902 127.493H832.261Z" fill="#2D2E2F"/>
<path d="M912.082 290.469C910.321 290.469 908.824 289.94 907.592 288.884C906.535 287.651 906.007 286.154 906.007 284.393V174.246H883.555C881.794 174.246 880.297 173.718 879.064 172.661C878.008 171.429 877.479 169.932 877.479 168.171V159.19C877.479 157.429 878.008 156.02 879.064 154.964C880.297 153.731 881.794 153.115 883.555 153.115H906.007V139.908C906.007 130.927 907.504 123.091 910.497 116.399C913.491 109.531 918.245 104.249 924.761 100.551C931.453 96.8525 940.345 95.0035 951.439 95.0035H967.288C969.049 95.0035 970.458 95.6199 971.514 96.8525C972.571 97.9091 973.099 99.3179 973.099 101.079V110.06C973.099 111.821 972.571 113.317 971.514 114.55C970.458 115.607 969.049 116.135 967.288 116.135H951.968C943.691 116.135 938.056 118.336 935.062 122.738C932.069 126.965 930.572 133.128 930.572 141.228V153.115H964.646C966.407 153.115 967.816 153.731 968.873 154.964C969.929 156.02 970.458 157.429 970.458 159.19V168.171C970.458 169.932 969.929 171.429 968.873 172.661C967.816 173.718 966.407 174.246 964.646 174.246H930.572V284.393C930.572 286.154 929.956 287.651 928.723 288.884C927.667 289.94 926.258 290.469 924.497 290.469H912.082Z" fill="#2D2E2F"/>
<path d="M1017.94 340.656C1016.53 340.656 1015.3 340.127 1014.24 339.071C1013.19 338.014 1012.66 336.782 1012.66 335.373C1012.66 334.668 1012.75 333.964 1012.92 333.26C1013.1 332.555 1013.45 331.675 1013.98 330.618L1034.32 282.28L983.867 163.152C982.987 161.039 982.546 159.542 982.546 158.662C982.546 157.077 983.075 155.756 984.131 154.7C985.188 153.643 986.509 153.115 988.093 153.115H1001.04C1002.8 153.115 1004.21 153.555 1005.26 154.435C1006.32 155.316 1007.02 156.373 1007.38 157.605L1047.53 253.753L1088.73 157.605C1089.26 156.373 1089.96 155.316 1090.84 154.435C1091.9 153.555 1093.4 153.115 1095.34 153.115H1107.75C1109.33 153.115 1110.66 153.643 1111.71 154.7C1112.77 155.756 1113.3 156.989 1113.3 158.398C1113.3 159.278 1112.86 160.863 1111.98 163.152L1036.96 336.165C1036.43 337.398 1035.64 338.454 1034.58 339.335C1033.7 340.215 1032.29 340.656 1030.36 340.656H1017.94Z" fill="#2D2E2F"/>
<path d="M1128.65 290.469C1126.89 290.469 1125.4 289.94 1124.16 288.884C1123.11 287.651 1122.58 286.154 1122.58 284.393V265.375C1122.58 263.614 1123.11 262.205 1124.16 261.149C1125.4 259.916 1126.89 259.3 1128.65 259.3H1147.67C1149.43 259.3 1150.84 259.916 1151.9 261.149C1153.13 262.205 1153.75 263.614 1153.75 265.375V284.393C1153.75 286.154 1153.13 287.651 1151.9 288.884C1150.84 289.94 1149.43 290.469 1147.67 290.469H1128.65Z" fill="#2D2E2F"/>
<path d="M1244.66 348.58C1232.69 348.58 1222.83 346.995 1215.08 343.825C1207.51 340.656 1201.52 336.782 1197.12 332.203C1192.89 327.801 1189.81 323.31 1187.87 318.732C1186.11 314.329 1185.14 310.808 1184.97 308.166C1184.79 306.405 1185.32 304.908 1186.55 303.676C1187.96 302.443 1189.46 301.827 1191.04 301.827H1203.72C1205.31 301.827 1206.63 302.179 1207.68 302.883C1208.74 303.588 1209.62 305.084 1210.32 307.374C1211.38 310.015 1212.97 312.921 1215.08 316.09C1217.19 319.436 1220.45 322.254 1224.85 324.543C1229.25 327.008 1235.42 328.241 1243.34 328.241C1251.62 328.241 1258.57 327.096 1264.21 324.807C1269.84 322.694 1274.07 318.82 1276.89 313.185C1279.88 307.726 1281.38 300.154 1281.38 290.469V272.507C1277.15 277.966 1271.61 282.544 1264.74 286.242C1257.87 289.764 1249.24 291.525 1238.85 291.525C1228.99 291.525 1220.54 289.852 1213.49 286.507C1206.45 282.985 1200.64 278.318 1196.06 272.507C1191.66 266.52 1188.31 259.74 1186.02 252.168C1183.91 244.596 1182.68 236.672 1182.33 228.395C1182.15 223.465 1182.15 218.622 1182.33 213.867C1182.68 205.591 1183.91 197.667 1186.02 190.095C1188.31 182.523 1191.66 175.743 1196.06 169.756C1200.64 163.769 1206.45 159.102 1213.49 155.756C1220.54 152.234 1228.99 150.473 1238.85 150.473C1249.42 150.473 1258.13 152.498 1265 156.549C1272.05 160.423 1277.77 165.177 1282.17 170.812V159.454C1282.17 157.693 1282.7 156.196 1283.76 154.964C1284.99 153.731 1286.49 153.115 1288.25 153.115H1300.4C1302.16 153.115 1303.65 153.731 1304.89 154.964C1306.12 156.196 1306.74 157.693 1306.74 159.454V287.563C1306.74 299.361 1304.71 309.839 1300.66 318.996C1296.79 328.153 1290.27 335.373 1281.11 340.656C1272.13 345.938 1259.98 348.58 1244.66 348.58ZM1244.13 270.394C1252.94 270.394 1259.98 268.369 1265.27 264.319C1270.73 260.268 1274.69 255.162 1277.15 248.998C1279.79 242.835 1281.2 236.584 1281.38 230.244C1281.55 227.779 1281.64 224.785 1281.64 221.263C1281.64 217.565 1281.55 214.484 1281.38 212.018C1281.2 205.679 1279.79 199.428 1277.15 193.264C1274.69 187.101 1270.73 181.994 1265.27 177.944C1259.98 173.894 1252.94 171.869 1244.13 171.869C1235.33 171.869 1228.29 173.894 1223 177.944C1217.72 181.818 1213.93 187.013 1211.65 193.529C1209.36 199.868 1208.04 206.736 1207.68 214.132C1207.51 218.71 1207.51 223.377 1207.68 228.131C1208.04 235.527 1209.36 242.483 1211.65 248.998C1213.93 255.338 1217.72 260.533 1223 264.583C1228.29 268.457 1235.33 270.394 1244.13 270.394Z" fill="#2D2E2F"/>
<path d="M1394.35 293.11C1380.96 293.11 1369.78 290.557 1360.8 285.45C1351.82 280.343 1344.95 273.299 1340.2 264.319C1335.44 255.162 1332.8 244.772 1332.27 233.15C1332.1 230.156 1332.01 226.37 1332.01 221.792C1332.01 217.037 1332.1 213.251 1332.27 210.434C1332.8 198.635 1335.44 188.246 1340.2 179.265C1345.13 170.284 1352.08 163.24 1361.07 158.133C1370.05 153.027 1381.14 150.473 1394.35 150.473C1407.55 150.473 1418.65 153.027 1427.63 158.133C1436.61 163.24 1443.48 170.284 1448.23 179.265C1453.16 188.246 1455.89 198.635 1456.42 210.434C1456.6 213.251 1456.69 217.037 1456.69 221.792C1456.69 226.37 1456.6 230.156 1456.42 233.15C1455.89 244.772 1453.25 255.162 1448.5 264.319C1443.74 273.299 1436.87 280.343 1427.89 285.45C1418.91 290.557 1407.73 293.11 1394.35 293.11ZM1394.35 272.771C1405.27 272.771 1413.98 269.337 1420.5 262.47C1427.01 255.426 1430.53 245.212 1431.06 231.829C1431.24 229.188 1431.33 225.842 1431.33 221.792C1431.33 217.742 1431.24 214.396 1431.06 211.754C1430.53 198.371 1427.01 188.246 1420.5 181.378C1413.98 174.334 1405.27 170.812 1394.35 170.812C1383.43 170.812 1374.62 174.334 1367.93 181.378C1361.42 188.246 1357.98 198.371 1357.63 211.754C1357.46 214.396 1357.37 217.742 1357.37 221.792C1357.37 225.842 1357.46 229.188 1357.63 231.829C1357.98 245.212 1361.42 255.426 1367.93 262.47C1374.62 269.337 1383.43 272.771 1394.35 272.771Z" fill="#2D2E2F"/>
<path d="M1524.75 290.469C1522.1 290.469 1519.99 289.852 1518.41 288.62C1517 287.387 1515.85 285.802 1514.97 283.865L1466.11 161.303C1465.75 160.423 1465.58 159.542 1465.58 158.662C1465.58 157.077 1466.11 155.756 1467.16 154.7C1468.22 153.643 1469.54 153.115 1471.12 153.115H1483.8C1485.74 153.115 1487.24 153.643 1488.29 154.7C1489.35 155.756 1489.97 156.725 1490.14 157.605L1530.56 261.677L1570.71 157.605C1571.06 156.725 1571.67 155.756 1572.56 154.7C1573.61 153.643 1575.11 153.115 1577.05 153.115H1589.99C1591.4 153.115 1592.63 153.643 1593.69 154.7C1594.92 155.756 1595.54 157.077 1595.54 158.662C1595.54 159.542 1595.36 160.423 1595.01 161.303L1545.88 283.865C1545.17 285.802 1544.03 287.387 1542.44 288.62C1541.03 289.852 1538.92 290.469 1536.1 290.469H1524.75Z" fill="#2D2E2F"/>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -1,3 +0,0 @@
<svg viewBox="0 0 563 670" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M553.831 334.751V335.974L555.03 335.731C555.104 335.716 555.181 335.708 555.262 335.708H557.846C558.483 335.708 559 336.224 559 336.862V418.708C559 419.345 558.483 419.862 557.846 419.862H555.262C555.181 419.862 555.104 419.853 555.03 419.838L553.831 419.595V420.818V955.446C553.831 970.12 547.528 991.015 535.312 1008.3C522.978 1025.76 503.794 1040.6 477.723 1040.6H82.2769C56.206 1040.6 37.022 1025.76 24.6885 1008.3C12.472 991.015 6.16923 970.12 6.16923 955.446V420.818V419.595L4.97026 419.838C4.89621 419.853 4.8188 419.862 4.73846 419.862H2.15385C1.51659 419.862 1 419.345 1 418.708V358.4C1 357.763 1.51659 357.246 2.15385 357.246H4.73846C4.8188 357.246 4.89621 357.254 4.97026 357.269L6.16923 357.513V356.289V347.588V346.364L4.97026 346.608C4.89621 346.623 4.8188 346.631 4.73846 346.631H2.15385C1.51659 346.631 1 346.114 1 345.477V285.169C1 284.532 1.51659 284.015 2.15385 284.015H4.73846C4.8188 284.015 4.89621 284.023 4.97026 284.038L6.16923 284.282V283.058V261.434V260.21L4.97026 260.454C4.89621 260.469 4.8188 260.477 4.73846 260.477H2.15385C1.51659 260.477 1 259.96 1 259.323V220.554C1 219.917 1.51659 219.4 2.15385 219.4H4.73846C4.8188 219.4 4.89624 219.408 4.97036 219.423L6.16923 219.666V218.443V86.1538C6.16923 71.4804 12.472 50.5848 24.6885 33.2974C37.022 15.8443 56.206 1 82.2769 1H477.723C503.794 1 522.978 15.8443 535.312 33.2974C547.528 50.5848 553.831 71.4804 553.831 86.1538V334.751ZM400.615 16.2308H399.615V17.2308V43.0769C399.615 49.6619 394.277 55 387.692 55H172.308C165.723 55 160.385 49.6619 160.385 43.0769V17.2308V16.2308H159.385H82.2769C62.6883 16.2308 47.6257 27.2304 37.1269 42.0872C26.5125 57.1076 21.4 74.9813 21.4 86.1538V955.446C21.4 966.619 26.5125 984.492 37.1269 999.513C47.6257 1014.37 62.6883 1025.37 82.2769 1025.37H477.723C497.312 1025.37 512.374 1014.37 522.873 999.513C533.487 984.492 538.6 966.619 538.6 955.446V86.1538C538.6 74.9813 533.488 57.1076 522.873 42.0872C512.374 27.2304 497.312 16.2308 477.723 16.2308H400.615Z" stroke="black" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="260.000000pt" height="260.000000pt" viewBox="0 0 260.000000 260.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,260.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1177 2586 c-67 -29 -117 -107 -117 -183 0 -12 -15 -24 -48 -37 -175
-69 -342 -229 -426 -406 -74 -156 -77 -183 -78 -657 l-1 -423 -123 -116 -124
-117 0 -109 0 -108 996 2 997 3 -1 105 -1 105 -123 117 -123 118 -1 310 c0
170 -3 313 -7 316 -4 4 -26 8 -50 9 -105 6 -216 59 -292 140 -152 161 -161
417 -22 583 35 42 31 48 -71 97 -42 20 -84 41 -92 45 -8 4 -18 28 -22 54 -14
100 -80 158 -182 162 -34 1 -74 -4 -89 -10z"/>
<path d="M1854 2297 c-64 -28 -95 -48 -134 -89 -65 -68 -99 -163 -94 -265 11
-271 317 -431 547 -286 163 103 215 311 122 482 -35 64 -91 114 -165 149 -77
37 -202 41 -276 9z"/>
<path d="M910 282 c0 -32 32 -116 59 -154 114 -161 427 -170 566 -16 42 47 75
119 75 165 l0 23 -350 0 c-332 0 -350 -1 -350 -18z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 425.2 428" style="enable-background:new 0 0 425.2 428;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1D70B8;}
.st1{fill:#0B0C0C;}
.st2{fill:#FFDD00;}
.st3{opacity:0.2;fill:#1D2F3A;}
.st4{opacity:0.5;fill:#F4F4F4;}
.st5{clip-path:url(#SVGID_2_);fill:#FFDD00;}
.st6{opacity:0.2;clip-path:url(#SVGID_2_);fill:#1D2F3A;}
.st7{opacity:0.5;clip-path:url(#SVGID_2_);}
.st8{fill:#F4F4F4;}
.st9{fill:#206FAB;}
</style>
<g id="Layer_1">
<circle class="st0" cx="212.6" cy="212.6" r="212.6"/>
</g>
<g id="Layer_2">
<rect x="91.9" y="248.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -164.6926 181.3616)" class="st1" width="89.3" height="81.7"/>
<rect x="76.9" y="258.5" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -150.6822 163.09)" class="st2" width="89.3" height="9.9"/>
<rect x="90.6" y="272.2" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -156.3567 176.7894)" class="st2" width="89.3" height="9.9"/>
<rect x="104.3" y="285.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -162.0312 190.4889)" class="st2" width="89.3" height="9.9"/>
<rect x="118" y="299.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -167.7057 204.1884)" class="st2" width="89.3" height="9.9"/>
<rect x="131.9" y="227.1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -143.1309 190.2927)" class="st3" width="52.5" height="81.7"/>
</g>
<g id="Layer_5">
<polygon class="st4" points="116.8,275.2 108.5,283.4 103,274.9 111.3,266.7 "/>
<polygon class="st4" points="127.6,291.8 119.3,300 113.8,291.5 122.1,283.3 "/>
<polygon class="st4" points="138.3,308.4 130.1,316.7 124.6,308.2 132.8,299.9 "/>
<polygon class="st4" points="149.1,325 140.9,333.3 135.4,324.8 143.6,316.6 "/>
</g>
<g id="Layer_6">
</g>
<g id="Layer_4">
<defs>
<ellipse id="SVGID_1_" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -22.5211 240.2509)" cx="278.7" cy="147.3" rx="23.7" ry="23.7"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<ellipse transform="matrix(0.7071 -0.7071 0.7071 0.7071 -22.5211 240.2509)" class="st5" cx="278.7" cy="147.3" rx="23.7" ry="23.7"/>
<ellipse transform="matrix(0.7071 -0.7071 0.7071 0.7071 -22.5211 240.2509)" class="st6" cx="278.7" cy="147.3" rx="23.7" ry="23.7"/>
<path class="st5" d="M264.8,127.7c15.8,2.7,30.8,17.7,33.5,33.5s-7.9,26.4-23.7,23.7s-30.8-17.7-33.5-33.5
C238.4,135.6,249,125,264.8,127.7z"/>
<g class="st7">
<rect x="260.6" y="117.2" transform="matrix(0.842 -0.5395 0.5395 0.842 -44.6945 168.5803)" class="st8" width="9.8" height="86.7"/>
</g>
</g>
<g id="Layer_9">
<ellipse transform="matrix(0.7071 -0.7071 0.7071 0.7071 -22.5211 240.2509)" class="st9" cx="278.7" cy="147.3" rx="15.8" ry="15.8"/>
</g>
<g id="Layer_3">
<path class="st1" d="M278,74.5l-168,168l73.6,73.6l168-168L278,74.5z M262,164c-9.2-9.2-9.2-24.2,0-33.5s24.2-9.2,33.5,0
s9.2,24.2,0,33.5S271.3,173.3,262,164z"/>
</g>
<g id="Layer_8">
</g>
<g id="Layer_10">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1,17 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6311_9575)">
<path d="M13.1356 105.339H93.2878C94.3236 105.339 95.1628 104.5 95.1628 103.464C95.1628 102.428 94.3236 101.589 93.2878 101.589H13.1356C11.6753 101.589 10.4873 100.401 10.4873 98.9407V26.0474H44.2016C41.9081 29.6852 40.5788 33.9892 40.5788 38.5982V46.6989C37.1923 47.1732 34.5773 50.0874 34.5773 53.6027V86.9362C34.5773 90.7812 37.7053 93.9092 41.5503 93.9092H86.8113C90.6563 93.9092 93.7843 90.7812 93.7843 86.9362V53.6029C93.7843 50.0877 91.1693 47.1734 87.7826 46.6992V38.5984C87.7826 33.9892 86.4533 29.6854 84.1598 26.0477H119.138V98.9409C119.138 100.401 117.95 101.589 116.49 101.589H102.282C101.246 101.589 100.407 102.428 100.407 103.464C100.407 104.5 101.246 105.339 102.282 105.339H116.49C120.018 105.339 122.888 102.469 122.888 98.9409V14.1582C122.888 9.20744 118.86 5.17969 113.909 5.17969H38.9261C37.8903 5.17969 37.0511 6.01894 37.0511 7.05469C37.0511 8.09044 37.8903 8.92969 38.9261 8.92969H113.909C116.792 8.92969 119.138 11.2752 119.138 14.1582V22.2974H81.3611C81.3201 22.2974 81.2798 22.3009 81.2393 22.3034C76.9391 17.8034 70.8815 14.9962 64.1805 14.9962C57.4533 14.9962 51.3748 17.8257 47.0716 22.3564C46.9241 22.3192 10.4873 22.2972 10.4873 22.2972V14.1582C10.4873 11.2752 12.8328 8.92969 15.7158 8.92969H30.1583C31.1941 8.92969 32.0333 8.09044 32.0333 7.05469C32.0333 6.01894 31.1941 5.17969 30.1583 5.17969H15.7158C10.7651 5.17969 6.7373 9.20744 6.7373 14.1582V98.9409C6.7373 102.469 9.60755 105.339 13.1356 105.339ZM90.0343 53.6029V86.9364C90.0343 88.7134 88.5886 90.1594 86.8113 90.1594H41.5503C39.7733 90.1594 38.3273 88.7137 38.3273 86.9364V53.6029C38.3273 51.8259 39.7731 50.3799 41.5503 50.3799H86.8113C88.5886 50.3802 90.0343 51.8259 90.0343 53.6029ZM74.5733 46.6302H53.7881V38.5984C53.7881 32.8677 58.4501 28.2057 64.1805 28.2057C69.911 28.2057 74.5731 32.8677 74.5731 38.5984V46.6302H74.5733ZM64.1808 18.7464C75.1271 18.7464 84.0326 27.6519 84.0326 38.5982V46.6299H78.3233V38.5982C78.3233 30.7999 71.979 24.4554 64.1808 24.4554C56.3826 24.4554 50.0383 30.7999 50.0383 38.5982V46.6299H44.3291V38.5982C44.3288 27.6519 53.2346 18.7464 64.1808 18.7464Z" fill="black"/>
<path d="M91.6201 17.4883C92.6558 17.4883 93.4951 16.649 93.4951 15.6133C93.4951 14.5775 92.6558 13.7383 91.6201 13.7383H89.5701C88.5343 13.7383 87.6951 14.5775 87.6951 15.6133C87.6951 16.649 88.5343 17.4883 89.5701 17.4883H91.6201Z" fill="black"/>
<path d="M100.641 17.4883C101.676 17.4883 102.516 16.649 102.516 15.6133C102.516 14.5775 101.676 13.7383 100.641 13.7383H98.5906C97.5548 13.7383 96.7156 14.5775 96.7156 15.6133C96.7156 16.649 97.5548 17.4883 98.5906 17.4883H100.641Z" fill="black"/>
<path d="M109.661 17.4883C110.697 17.4883 111.536 16.649 111.536 15.6133C111.536 14.5775 110.697 13.7383 109.661 13.7383H107.611C106.575 13.7383 105.736 14.5775 105.736 15.6133C105.736 16.649 106.575 17.4883 107.611 17.4883H109.661Z" fill="black"/>
<path d="M126.125 113.899H105.969C105.146 110.921 102.414 108.728 99.1775 108.728C95.9412 108.728 93.2095 110.921 92.3863 113.899H35.6138C34.7905 110.921 32.0588 108.728 28.8228 108.728C25.5865 108.728 22.8547 110.921 22.0315 113.899H1.875C0.83925 113.899 0 114.738 0 115.774C0 116.81 0.83925 117.649 1.875 117.649H22.0315C22.8547 120.627 25.5865 122.82 28.8228 122.82C32.0588 122.82 34.7905 120.627 35.6138 117.649H92.3865C93.2097 120.627 95.9415 122.82 99.1777 122.82C102.414 122.82 105.146 120.627 105.969 117.649H126.125C127.161 117.649 128 116.81 128 115.774C128 114.738 127.161 113.899 126.125 113.899ZM28.8228 119.07C27.005 119.07 25.5263 117.591 25.5263 115.774C25.5263 113.956 27.005 112.477 28.8228 112.477C30.6402 112.477 32.119 113.956 32.119 115.774C32.119 117.591 30.6402 119.07 28.8228 119.07ZM99.1775 119.07C97.3597 119.07 95.881 117.591 95.881 115.774C95.881 113.956 97.3597 112.477 99.1775 112.477C100.995 112.477 102.474 113.956 102.474 115.774C102.474 117.591 100.995 119.07 99.1775 119.07Z" fill="black"/>
<path d="M74.6482 63.1457C73.9162 62.4137 72.7289 62.4135 71.9967 63.146C71.2644 63.8782 71.2644 65.0655 71.9969 65.7975L76.4694 70.2697L71.9967 74.7422C71.2644 75.4747 71.2644 76.6617 71.9967 77.394C72.3629 77.76 72.8429 77.943 73.3227 77.943C73.8024 77.943 74.2824 77.76 74.6487 77.394L80.4469 71.5957C80.7984 71.244 80.9959 70.7672 80.9959 70.2697C80.9959 69.7722 80.7984 69.2957 80.4467 68.9437L74.6482 63.1457Z" fill="black"/>
<path d="M51.8922 70.2697L56.3647 65.7974C57.0972 65.0652 57.0972 63.8779 56.365 63.1459C55.6327 62.4139 54.4455 62.4134 53.7135 63.1456L47.9152 68.9436C47.5635 69.2954 47.366 69.7722 47.366 70.2697C47.366 70.7672 47.5635 71.2436 47.915 71.5956L53.7132 77.3939C54.0795 77.7599 54.5595 77.9429 55.0392 77.9429C55.519 77.9429 55.999 77.7599 56.3652 77.3939C57.0975 76.6614 57.0975 75.4744 56.3652 74.7421L51.8922 70.2697Z" fill="black"/>
<path d="M66.0639 62.6508C65.0587 62.4026 64.0422 63.0161 63.7937 64.0211L60.9274 75.6176C60.6792 76.6228 61.2927 77.6393 62.2977 77.8878C62.4487 77.9251 62.6002 77.9431 62.7492 77.9431C63.5912 77.9431 64.3569 77.3716 64.5679 76.5176L67.4342 64.9211C67.6824 63.9158 67.0689 62.8996 66.0639 62.6508Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_6311_9575">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,12 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6311_9539)">
<path d="M107.26 81.0871C108.302 81.0871 109.145 80.2431 109.145 79.2021V61.7874C109.145 60.7464 108.302 59.9023 107.26 59.9023H103.538C102.497 59.9023 101.653 60.7464 101.653 61.7874C101.653 62.8285 102.497 63.6725 103.538 63.6725H105.375V79.2021C105.375 80.2431 106.219 81.0871 107.26 81.0871Z" fill="black"/>
<path d="M105.03 95.3332C104.018 95.0871 102.999 95.7069 102.752 96.7186L97.7374 117.306C97.6188 117.793 97.2262 117.927 97.0625 117.963C96.8997 117.998 96.486 118.037 96.1776 117.642L59.5854 70.7809L122.95 13.8064L115.318 45.1362C115.072 46.1479 115.692 47.1676 116.703 47.4139C116.853 47.4503 117.003 47.4679 117.151 47.4679C118 47.4679 118.771 46.8901 118.981 46.0285L127.914 9.35889C128.13 8.46888 127.846 7.53061 127.173 6.91004C126.499 6.28972 125.541 6.08387 124.671 6.37267L34.2959 36.438C33.3079 36.7667 32.7735 37.8339 33.1023 38.8217C33.4308 39.8095 34.4985 40.3441 35.4858 40.0153L119.097 12.2011L56.5961 68.3984L5.56399 49.9696L27.7773 42.58C28.7653 42.2513 29.2997 41.1841 28.9709 40.1963C28.6424 39.2088 27.5755 38.6744 26.5874 39.0027L1.98209 47.1877C0.793983 47.5828 0.0163241 48.647 0.000238096 49.8989C-0.0153452 51.1506 0.735168 52.2342 1.91221 52.6592L37.0421 65.3453V98.9988C37.0421 100.55 37.8471 101.934 39.1953 102.701C39.8571 103.077 40.5798 103.265 41.3014 103.265C42.0504 103.265 42.7986 103.062 43.4772 102.659L51.7857 97.7167C52.6807 97.1843 52.9743 96.0276 52.4422 95.1328C51.9098 94.2378 50.7532 93.9442 49.8584 94.4763L41.5499 99.4185C41.3479 99.5386 41.1717 99.4871 41.0591 99.4233C40.9465 99.3592 40.8122 99.2335 40.8122 98.9988V66.7068L55.8657 72.1429L65.8687 84.9529L56.7635 90.3691C55.8685 90.9015 55.5749 92.0581 56.107 92.9529C56.4597 93.5456 57.0863 93.8746 57.729 93.8746C58.057 93.8746 58.3887 93.7891 58.6908 93.6094L68.2077 87.9484L93.2062 119.962C94.1045 121.113 95.4456 121.755 96.8595 121.755C97.1892 121.755 97.523 121.72 97.856 121.648C99.6162 121.27 100.974 119.948 101.401 118.198L106.416 97.6108C106.662 96.5994 106.042 95.5797 105.03 95.3332Z" fill="black"/>
<path d="M126.484 72.7292C125.468 72.51 124.464 73.1573 124.244 74.1747C122.533 82.1121 115.391 87.873 107.26 87.873C97.6782 87.873 89.8825 80.0773 89.8825 70.495C89.8825 60.9128 97.6782 53.1171 107.26 53.1171C115.446 53.1171 122.599 58.9234 124.269 66.9232C124.482 67.9424 125.482 68.5969 126.5 68.3832C127.519 68.1703 128.173 67.1718 127.96 66.1526C125.927 56.4145 117.221 49.3467 107.261 49.3467C95.5995 49.3467 86.1125 58.8337 86.1125 70.4948C86.1125 82.1559 95.5995 91.6429 107.261 91.6429C112.123 91.6429 116.871 89.9511 120.63 86.8791C124.335 83.8517 126.927 79.6216 127.93 74.9685C128.149 73.9515 127.502 72.9484 126.484 72.7292Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_6311_9539">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,19 +1 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><g><path d="m488.19 30.63h-48.55c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h48.551c4.857 0 8.81 3.952 8.81 8.811v39.835h-482.001v-39.836c0-4.858 3.952-8.811 8.81-8.811h380.83c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-380.83c-13.129.001-23.81 10.683-23.81 23.811v403.12c0 13.129 10.681 23.811 23.81 23.811h69c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-69c-4.857 0-8.81-3.952-8.81-8.811v-348.285h482v348.285c0 4.858-3.952 8.811-8.81 8.811h-360.38c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h360.38c13.129 0 23.81-10.682 23.81-23.811v-403.12c0-13.128-10.681-23.81-23.81-23.81z"/><path d="m185.41 134.28h-131.91c-7.444 0-13.5 6.056-13.5 13.5v66.13c0 7.444 6.056 13.5 13.5 13.5h405c7.444 0 13.5-6.056 13.5-13.5v-66.13c0-7.444-6.056-13.5-13.5-13.5h-238.09c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h236.59v63.13h-321.142v-63.13h49.552c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5zm-64.552 78.13h-65.858v-63.13h65.858z"/><path d="m67.935 188.478 11.756 9.094c1.321 1.021 2.937 1.567 4.589 1.567.329 0 .66-.021.989-.065 1.986-.265 3.783-1.312 4.992-2.909l18.666-24.671c2.499-3.304 1.847-8.007-1.456-10.507-3.304-2.499-8.006-1.847-10.507 1.456l-14.092 18.626-5.76-4.456c-3.275-2.533-7.985-1.934-10.521 1.344-2.534 3.275-1.933 7.985 1.344 10.521z"/><path d="m153.684 180.845c0 4.143 3.357 7.5 7.5 7.5h72.66c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-72.66c-4.143 0-7.5 3.357-7.5 7.5z"/><path d="m431.671 188.345c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-162.823c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5z"/><path d="m53.504 441.366h404.992c7.444 0 13.5-6.056 13.5-13.5v-66.132c0-7.444-6.056-13.5-13.5-13.5h-404.992c-7.444 0-13.5 6.056-13.5 13.5v66.132c0 7.445 6.056 13.5 13.5 13.5zm403.492-15h-321.138v-63.132h321.138zm-401.992-63.132h65.854v63.132h-65.854z"/><path d="m98.342 394.8 6.604-6.604c2.929-2.93 2.929-7.678 0-10.607-2.93-2.928-7.678-2.928-10.607 0l-6.604 6.604-6.604-6.604c-2.93-2.928-7.678-2.928-10.607 0-2.929 2.93-2.929 7.678 0 10.607l6.604 6.604-6.604 6.604c-2.929 2.93-2.929 7.678 0 10.607 1.465 1.464 3.385 2.196 5.304 2.196s3.839-.732 5.304-2.196l6.604-6.604 6.604 6.604c1.465 1.464 3.385 2.196 5.304 2.196s3.839-.732 5.304-2.196c2.929-2.93 2.929-7.678 0-10.607z"/><path d="m161.184 402.3h72.66c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-72.66c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5z"/><path d="m268.848 402.3h162.823c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-162.823c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5z"/><path d="m458.496 241.257h-404.992c-7.444 0-13.5 6.056-13.5 13.5v66.132c0 7.444 6.056 13.5 13.5 13.5h404.992c7.444 0 13.5-6.056 13.5-13.5v-66.132c0-7.445-6.056-13.5-13.5-13.5zm-403.492 15h65.854v63.132h-65.854zm401.992 63.132h-321.138v-63.132h321.138z"/><path d="m107.471 269.506c-3.304-2.499-8.006-1.847-10.507 1.456l-14.092 18.626-5.76-4.456c-3.275-2.534-7.985-1.933-10.521 1.344-2.534 3.276-1.933 7.986 1.344 10.521l11.756 9.094c1.321 1.021 2.937 1.567 4.589 1.567.329 0 .66-.021.989-.065 1.986-.265 3.783-1.312 4.992-2.909l18.666-24.671c2.499-3.304 1.846-8.007-1.456-10.507z"/><path d="m359.011 295.322h72.66c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-72.66c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5z"/><path d="m161.184 295.322h162.823c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-162.823c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5z"/><g><circle cx="42.069" cy="69.954" r="7.5"/><circle cx="76.192" cy="69.954" r="7.5"/><circle cx="110.31" cy="69.954" r="7.5"/></g></g><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,12 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6311_9544)">
<path d="M31.5182 96.3591C31.7232 97.3748 32.7129 98.0311 33.7269 97.8261C38.9609 96.7696 42.8322 94.7671 45.6829 92.5183C48.5337 94.7671 52.4049 96.7698 57.6389 97.8261C58.6529 98.0311 59.6429 97.3746 59.8477 96.3591C60.0524 95.3441 59.3957 94.3551 58.3807 94.1503C54.1179 93.2898 50.8949 91.7473 48.4574 89.9433C51.1487 87.0021 52.9734 83.4338 53.8114 79.8881H58.3314C59.3672 79.8881 60.2064 79.0486 60.2064 78.0131C60.2064 76.9776 59.3672 76.1381 58.3314 76.1381H47.5584V71.7363C47.5584 70.7008 46.7192 69.8613 45.6834 69.8613C44.6477 69.8613 43.8084 70.7008 43.8084 71.7363V76.1381H33.0344C31.9987 76.1381 31.1594 76.9776 31.1594 78.0131C31.1594 79.0486 31.9987 79.8881 33.0344 79.8881H37.5544C38.3907 83.4258 40.2092 86.9866 42.8907 89.9241C40.4537 91.7403 37.2347 93.2923 32.9852 94.1501C31.9699 94.3548 31.3132 95.3441 31.5182 96.3591ZM41.4162 79.8881H49.9329C49.3807 81.7618 48.1822 84.6693 45.6567 87.4271C43.1337 84.6718 41.9552 81.7671 41.4162 79.8881Z" fill="black"/>
<path d="M82.3162 18.748C81.5372 18.748 80.839 19.23 80.5627 19.9585L71.3715 44.2103C71.0045 45.1785 71.492 46.261 72.4602 46.628C73.429 46.995 74.5112 46.5073 74.8782 45.5393L76.6965 40.7418C76.7585 40.748 76.8212 40.7513 76.885 40.7513H87.84C87.873 40.7513 87.905 40.748 87.9375 40.7463L89.754 45.539C90.1212 46.5075 91.2042 46.9945 92.172 46.6278C93.1402 46.2608 93.6277 45.1783 93.2607 44.21L84.0695 19.9583C83.7935 19.23 83.0955 18.748 82.3162 18.748ZM78.1142 37.0013L82.3162 25.9138L86.5182 37.0013H78.1142Z" fill="black"/>
<path d="M11.7858 116.457H16.6226C18.1479 116.457 19.5237 117.42 20.0462 118.853L22.5182 125.628C23.0439 127.069 24.3747 128 25.9087 128C27.4427 128 28.7737 127.069 29.2997 125.628L31.7715 118.852C32.2943 117.42 33.67 116.457 35.195 116.457H53.6349C54.6707 116.457 55.5099 115.618 55.5099 114.582C55.5099 113.547 54.6707 112.707 53.6349 112.707H35.1953C32.1008 112.707 29.3092 114.66 28.2487 117.567L25.9092 123.98L23.5694 117.567C22.5089 114.66 19.7174 112.707 16.6229 112.707H11.7858C7.35481 112.707 3.75003 109.103 3.75003 104.672V62.0325C3.75003 57.6015 7.35481 53.9967 11.7858 53.9967H36.633V54.4249C36.633 60.9237 41.9201 66.2108 48.4189 66.2108H87.6172V104.672C87.6172 109.103 84.0122 112.708 79.5811 112.708H62.3852C61.3495 112.708 60.5102 113.547 60.5102 114.583C60.5102 115.618 61.3495 116.458 62.3852 116.458H79.5811C86.0799 116.458 91.3672 111.171 91.3672 104.672V66.2108H92.805C94.33 66.2108 95.706 67.1735 96.2285 68.606L98.7003 75.3813C99.2263 76.8226 100.558 77.7536 102.091 77.7536C103.626 77.7536 104.956 76.8221 105.482 75.3813L107.954 68.606C108.477 67.1735 109.852 66.2108 111.377 66.2108H116.214C122.713 66.2108 128 60.9237 128 54.4249V38.8768C128 37.8413 127.161 37.0018 126.125 37.0018C125.089 37.0018 124.25 37.8413 124.25 38.8768V54.4249C124.25 58.856 120.645 62.4607 116.214 62.4607H111.377C108.283 62.4607 105.491 64.4138 104.431 67.3205L102.091 73.7338L99.7515 67.3205C98.6908 64.4138 95.899 62.4607 92.805 62.4607C91.289 62.4607 50.0484 62.4607 48.4189 62.4607C43.9878 62.4607 40.3831 58.856 40.3831 54.4249C40.3831 52.6232 40.3831 14.9124 40.3831 11.7856C40.3831 7.35481 43.9878 3.75003 48.4186 3.75003H116.214C120.645 3.75003 124.25 7.35481 124.25 11.7858V30.1267C124.25 31.1622 125.089 32.0018 126.125 32.0018C127.161 32.0018 128 31.1622 128 30.1267V11.7858C128 5.28704 122.713 0 116.214 0H48.4186C41.9201 0 36.6328 5.28704 36.6328 11.7858V50.2466H11.7858C5.28729 50.2466 0 55.5337 0 62.0325V104.672C0 111.17 5.28729 116.457 11.7858 116.457Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_6311_9544">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><g><path d="m291.532 66.273c4.142 0 7.5-3.357 7.5-7.5v-.057c0-4.143-3.358-7.472-7.5-7.472s-7.5 3.386-7.5 7.528 3.358 7.501 7.5 7.501z"/><path d="m254.455 66.273c4.142 0 7.5-3.357 7.5-7.5v-.057c0-4.143-3.358-7.472-7.5-7.472s-7.5 3.386-7.5 7.528 3.358 7.501 7.5 7.501z"/><path d="m217.378 66.273c4.142 0 7.5-3.357 7.5-7.5v-.057c0-4.143-3.358-7.472-7.5-7.472s-7.5 3.386-7.5 7.528 3.358 7.501 7.5 7.501z"/><path d="m123.925 51.273-76.763-.057c-.002 0-.003 0-.005 0-4.14 0-7.497 3.354-7.5 7.494-.003 4.143 3.353 7.503 7.495 7.506l76.763.057h.005c4.14 0 7.497-3.354 7.5-7.494.002-4.142-3.353-7.503-7.495-7.506z"/><path d="m287.507 124.863h-111.062c-6.933 0-12.574 5.641-12.574 12.574v33.479c0 6.948 5.641 12.602 12.574 12.602h111.062c6.933 0 12.574-5.653 12.574-12.602v-33.479c0-6.933-5.641-12.574-12.574-12.574zm-2.426 43.655h-106.209v-28.654h106.209z"/><path d="m292.581 210.217h-121.209c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h121.209c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m189.882 245.736h-18.51c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h18.51c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m292.581 245.736h-62.05c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h62.05c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m444.802 450.744h-.255c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h.255c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m490.667 301.75h-59.114v-129.049c0-11.669-9.494-21.163-21.163-21.163h-71.7v-114.592c0-11.404-9.278-20.682-20.682-20.682h-297.327c-11.404.001-20.681 9.278-20.681 20.682v241.718c0 11.419 9.277 20.709 20.681 20.709h33.801v97.131c0 11.669 9.494 21.163 21.163 21.163h104.223l-8.866 29.646h-14.994c-13.342 0-24.196 10.867-24.196 24.226v11.707c0 6.887 5.603 12.489 12.489 12.489h197.434c6.887 0 12.489-5.603 12.489-12.489v-11.707c0-13.358-10.867-24.226-24.225-24.226h-14.991l-8.847-29.646h71.443v56.735c0 11.763 9.557 21.333 21.305 21.333h91.758c11.763 0 21.333-9.57 21.333-21.333v-151.346c0-11.748-9.57-21.306-21.333-21.306zm-475.667-23.086v-177.438h137.039c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5h-137.039v-49.28c0-3.133 2.548-5.682 5.681-5.682h297.327c3.133 0 5.682 2.549 5.682 5.682v49.279h-137.011c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h137.011v177.438c0 3.147-2.549 5.709-5.682 5.709h-297.327c-3.133.001-5.681-2.56-5.681-5.708zm303.008 20.709c11.404 0 20.682-9.29 20.682-20.709v-112.126h71.7c3.398 0 6.163 2.765 6.163 6.163v129.049h-17.644c-11.748 0-21.305 9.558-21.305 21.306v41.683h-308.122v-65.365h248.526zm21.215 172.166v9.196h-192.411v-9.196c0-5.087 4.125-9.226 9.196-9.226h173.991c5.086 0 9.224 4.139 9.224 9.226zm-79.014-68.872c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h30.298l8.847 29.646h-112.696l8.866-29.646h30.188c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5h-150.067c-3.398 0-6.163-2.765-6.163-6.163v-16.766h308.123v22.929zm236.791 71.735c0 3.492-2.841 6.333-6.333 6.333h-91.758c-3.477 0-6.305-2.841-6.305-6.333v-151.346c0-3.477 2.828-6.306 6.305-6.306h91.758c3.492 0 6.333 2.829 6.333 6.306z"/><path d="m127.661 124.863h-76.451c-6.949 0-12.603 5.641-12.603 12.574v110.696c0 6.949 5.653 12.603 12.603 12.603h76.451c6.949 0 12.602-5.653 12.602-12.603v-110.695c0-6.934-5.653-12.575-12.602-12.575zm-2.398 120.873h-71.655v-105.873h71.655z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,374 +0,0 @@
(function (window) {
if (document.getElementById('activityChartContainer')) {
let currentType = 'service';
const tableContainer = document.getElementById('activityContainer');
const currentUserName = tableContainer.getAttribute('data-currentUserName');
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
const COLORS = {
delivered: '#0076d6',
failed: '#fa9441',
pending: '#C7CACE',
text: '#666'
};
const FONT_SIZE = 16;
const FONT_WEIGHT = 'bold';
const MAX_Y = 120;
const createChart = function(containerId, labels, deliveredData, failedData, pendingData) {
const container = d3.select(containerId);
container.selectAll('*').remove(); // Clear any existing content
const margin = { top: 60, right: 20, bottom: 40, left: 20 }; // Adjusted top margin for legend
const width = container.node().getBoundingClientRect().width - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = container.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let tooltip = d3.select('#tooltip');
if (tooltip.empty()) {
tooltip = d3.select('body').append('div')
.attr('id', 'tooltip')
.style('display', 'none');
}
// Calculate total messages
const totalMessages = d3.sum(deliveredData) + d3.sum(failedData) + d3.sum(pendingData);
// Create legend only if there are messages
const legendContainer = d3.select('.chart-legend');
legendContainer.selectAll('*').remove(); // Clear any existing legend
if (totalMessages > 0) {
// Show legend if there are messages
const legendData = [
{ label: 'Delivered', color: COLORS.delivered },
{ label: 'Failed', color: COLORS.failed },
{ label: 'Pending', color: COLORS.pending }
];
const legendItem = legendContainer.selectAll('.legend-item')
.data(legendData)
.enter()
.append('div')
.attr('class', 'legend-item');
legendItem.append('div')
.attr('class', 'legend-rect')
.style('background-color', d => d.color)
.style('display', 'inline-block')
.style('margin-right', '5px');
legendItem.append('span')
.attr('class', 'legend-label')
.text(d => d.label);
// Ensure the legend is shown
legendContainer.style('display', 'flex');
} else {
// Hide the legend if there are no messages
legendContainer.style('display', 'none');
}
const x = d3.scaleBand()
.domain(labels)
.range([0, width])
.padding(0.1);
// Adjust the y-axis domain to add some space above the tallest bar
const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0) + (pendingData[i] || 0)));
const y = d3.scaleSymlog()
.domain([0, maxY + 2]) // Add 2 units of space at the top
.nice()
.range([height, 0]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
// Generate the y-axis with whole numbers
const yAxis = d3.axisLeft(y)
.ticks(Math.min(maxY + 2, 3))
.tickFormat(d3.format('d')); // Ensure whole numbers on the y-axis
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
// Data for stacking
const stackData = labels.map((label, i) => ({
label: label,
delivered: deliveredData[i],
failed: failedData[i] || 0,
pending: pendingData[i] || 0
}));
// Stack the data
const stack = d3.stack()
.keys(['delivered', 'failed', 'pending'])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
const series = stack(stackData);
// Color scale
const color = d3.scaleOrdinal()
.domain(['delivered', 'failed', 'pending'])
.range([COLORS.delivered, COLORS.failed, COLORS.pending]);
// Create bars with animation
const barGroups = svg.selectAll('.bar-group')
.data(series)
.enter()
.append('g')
.attr('class', 'bar-group')
.attr('fill', d => color(d.key));
const minBarHeight = 5;
barGroups.selectAll('rect')
.data(d => d)
.enter()
.append('rect')
.filter(d => d[1] - d[0] > 0)
.attr('x', d => x(d.data.label))
.attr('y', height)
.attr('height', 0)
.attr('width', x.bandwidth())
.on('mouseover', function(event, d) {
const key = d3.select(this.parentNode).datum().key;
const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
tooltip.style('display', 'block')
.html(`${d.data.label}<br>${capitalizedKey}: ${d.data[key]}`);
})
.on('mousemove', function(event) {
tooltip.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY - 20}px`);
})
.on('mouseout', function() {
tooltip.style('display', 'none');
})
.transition()
.duration(1000)
.attr('y', d => y(d[1]))
.attr('height', d => {
const calculatedHeight = y(d[0]) - y(d[1]);
return calculatedHeight < minBarHeight ? minBarHeight : calculatedHeight;
}); };
// Function to create an accessible table
const createTable = function(tableId, chartType, labels, deliveredData, failedData, pendingData) {
const table = document.getElementById(tableId);
table.innerHTML = ""; // Clear previous data
const captionText = document.querySelector(`#${chartType} .chart-subtitle`).textContent;
const caption = document.createElement('caption');
caption.textContent = captionText;
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// Create table header
const headerRow = document.createElement('tr');
const headers = ['Day', 'Delivered', 'Failed', 'Pending'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
// Create table body
labels.forEach((label, index) => {
const row = document.createElement('tr');
const cellDay = document.createElement('td');
cellDay.textContent = label;
row.appendChild(cellDay);
const cellDelivered = document.createElement('td');
cellDelivered.textContent = deliveredData[index];
row.appendChild(cellDelivered);
const cellFailed = document.createElement('td');
cellFailed.textContent = failedData[index];
row.appendChild(cellFailed);
const cellPending = document.createElement('td');
cellPending.textContent = pendingData[index];
row.appendChild(cellPending);
tbody.appendChild(row);
});
table.appendChild(caption);
table.appendChild(thead);
table.append(tbody);
};
const fetchData = function(type) {
var ctx = document.getElementById('weeklyChart');
if (!ctx) {
return;
}
var userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
var url = type === 'service'
? `/services/${currentServiceId}/daily-stats.json?timezone=${encodeURIComponent(userTimezone)}`
: `/services/${currentServiceId}/daily-stats-by-user.json?timezone=${encodeURIComponent(userTimezone)}`;
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
labels = [];
deliveredData = [];
failedData = [];
pendingData = [];
let totalMessages = 0;
for (var dateString in data) {
if (data.hasOwnProperty(dateString)) {
const dateParts = dateString.split('-');
const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`;
labels.push(formattedDate);
deliveredData.push(data[dateString].sms.delivered);
failedData.push(data[dateString].sms.failure);
pendingData.push(data[dateString].sms.pending || 0);
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure + data[dateString].sms.pending;
}
}
// Check if there are no messages sent
const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`);
if (totalMessages === 0) {
// Remove existing chart and render the alert message
d3.select('#weeklyChart').selectAll('*').remove();
d3.select('#weeklyChart')
.append('div')
.html(`
<div class="usa-alert usa-alert--info usa-alert--slim">
<div class="usa-alert__body">
<p class="usa-alert__text">
No messages sent in the last 7 days
</p>
</div>
</div>
`);
// Hide the subtitle
if (subTitle) {
subTitle.style.display = 'none';
}
} else {
// If there are messages, create the chart and table
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
}
return data;
})
.catch(error => console.error('Error fetching daily stats:', error));
};
setInterval(() => fetchData(currentType), 25000);
const handleDropdownChange = function(event) {
const selectedValue = event.target.value;
currentType = selectedValue;
const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`);
const selectElement = document.getElementById('options');
const selectedText = selectElement.options[selectElement.selectedIndex].text;
subTitle.textContent = `${selectedText} - last 7 days`;
fetchData(selectedValue);
const liveRegion = document.getElementById('aria-live-account');
liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`;
const tableHeading = document.querySelector('#tableActivity h2');
const senderColumns = document.querySelectorAll('.sender-column');
const allRows = document.querySelectorAll('#activity-table tbody tr');
const caption = document.querySelector('#activity-table caption');
if (selectedValue === 'individual') {
tableHeading.textContent = 'My activity';
caption.textContent = `Table showing the sent jobs for ${currentUserName}`;
senderColumns.forEach(col => {
col.style.display = 'none';
});
allRows.forEach(row => row.style.display = 'none');
const userRows = Array.from(allRows).filter(row => {
const senderCell = row.querySelector('.sender-column');
const rowSender = senderCell ? senderCell.textContent.trim() : '';
return rowSender === currentUserName;
});
if (userRows.length > 0) {
userRows.slice(0, 5).forEach(row => {
row.style.display = '';
});
} else {
const emptyMessageRow = Array.from(allRows).find(row => {
return row.querySelector('.table-empty-message');
});
if (emptyMessageRow) {
emptyMessageRow.style.display = '';
}
}
} else {
tableHeading.textContent = 'Service activity';
caption.textContent = `Table showing the sent jobs for service`;
senderColumns.forEach(col => {
col.style.display = '';
});
allRows.forEach((row, index) => {
row.style.display = (index < 5) ? '' : 'none';
});
}
};
document.addEventListener('DOMContentLoaded', function() {
// Initialize activityChart chart and table with service data by default
fetchData(currentType);
const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr'));
allRows.forEach((row, index) => {
row.style.display = (index < 5) ? '' : 'none';
});
const dropdown = document.getElementById('options');
dropdown.addEventListener('change', handleDropdownChange);
});
// Resize chart on window resize
window.addEventListener('resize', function() {
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) {
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
}
});
// Export functions for testing
window.createChart = createChart;
window.createTable = createTable;
window.handleDropdownChange = handleDropdownChange;
window.fetchData = fetchData;
}
})(window);

View File

@@ -0,0 +1,27 @@
(function(Modules) {
"use strict";
Modules.Autofocus = function() {
this.start = function(component) {
var $component = $(component),
forceFocus = $component.data('forceFocus');
// if the page loads with a scroll position, we can't assume the item to focus onload
// is still where users intend to start
if (($(window).scrollTop() > 0) && !forceFocus) { return; }
// See if the component itself is something we want to send focus to
var target = $component.filter('input, textarea, select');
// Otherwise look inside the component to see if there are any elements
// we want to send focus to
if (target.length === 0) {
target = $('input, textarea, select', $component);
}
target.eq(0).trigger('focus');
};
};
})(window.GOVUK.Modules);

View File

@@ -63,9 +63,9 @@
const buttonContent = this.buttonContent[buttonState](this.fieldLabel);
const stickyClass = expanded ? ' js-stick-at-bottom-when-scrolling' : '';
return $(`<div class="selection-footer${stickyClass} margin-top-2">
return $(`<div class="selection-footer${stickyClass}">
<button
class="usa-button usa-button--outline selection-footer__button"
class="govuk-button govuk-button--secondary selection-footer__button"
aria-expanded="${expanded ? 'true' : 'false'}"
aria-controls="${this.fieldsetId}">
${buttonContent}
@@ -78,6 +78,8 @@
this.module.$formGroup.append(this.$el);
// make footer sticky if expanded, clear up from it being sticky if not
GOVUK.stickAtBottomWhenScrolling.recalculate();
};
function CollapsibleCheckboxes () {}
@@ -95,7 +97,6 @@
this.total = this.$checkboxes.length;
this.legendText = this.$fieldset.find('legend').first().text().trim();
this.expanded = false;
this.checkUncheckButtonsAdded = false;
this.addHeadingHideLegend();
@@ -123,44 +124,6 @@
this.$fieldset.find('legend').addClass('usa-sr-only');
};
CollapsibleCheckboxes.prototype.addCheckUncheckAllButtons = function() {
const $buttonsContainer = $('<div class="check-uncheck-all-buttons margin-bottom-2"></div>');
this.$toggleAllButton = $('<button type="button" class="usa-button usa-button--outline usa-button--small">Select all</button>');
$buttonsContainer.append(this.$toggleAllButton);
this.summary.$el.after($buttonsContainer);
this.$toggleAllButton.on('click', this.toggleAll.bind(this));
this.updateToggleButtonText();
};
CollapsibleCheckboxes.prototype.toggleAll = function(e) {
e.preventDefault();
const allChecked = this.$checkboxes.filter(':checked').length === this.$checkboxes.length;
if (allChecked) {
this.$checkboxes.prop('checked', false);
} else {
this.$checkboxes.prop('checked', true);
}
this.handleSelection();
this.updateToggleButtonText();
};
CollapsibleCheckboxes.prototype.updateToggleButtonText = function() {
if (!this.$toggleAllButton) return;
const checkedCount = this.$checkboxes.filter(':checked').length;
const allChecked = checkedCount === this.$checkboxes.length;
if (allChecked) {
this.$toggleAllButton.text('Deselect all');
} else {
this.$toggleAllButton.text('Select all');
}
};
CollapsibleCheckboxes.prototype.expand = function(e) {
if (e !== undefined) { e.preventDefault(); }
@@ -169,15 +132,6 @@
this.expanded = true;
this.summary.update(this.getSelection());
this.footer.update(this.expanded);
if (!this.checkUncheckButtonsAdded) {
this.addCheckUncheckAllButtons();
this.checkUncheckButtonsAdded = true;
} else {
if (this.$toggleAllButton) {
this.$toggleAllButton.parent().show();
}
}
}
// shift focus whether expanded or not
@@ -191,10 +145,6 @@
this.expanded = false;
this.summary.update(this.getSelection());
this.footer.update(this.expanded);
if (this.$toggleAllButton) {
this.$toggleAllButton.parent().hide();
}
}
// shift focus whether expanded or not
@@ -209,12 +159,11 @@
};
CollapsibleCheckboxes.prototype.handleSelection = function(e) {
this.summary.update(this.getSelection(), this.total, this.fieldLabel);
this.updateToggleButtonText();
};
CollapsibleCheckboxes.prototype.bindEvents = function() {
const self = this;
this.$formGroup.on('click', '.usa-button', this.handleClick.bind(this));
this.$formGroup.on('click', '.govuk-button', this.handleClick.bind(this));
this.$checkboxes.on('click', this.handleSelection.bind(this));
this.summary.bindEvents(this);

View File

@@ -0,0 +1,29 @@
(function(Modules) {
"use strict";
let isSixDigitHex = value => value.match(/^#[0-9A-F]{6}$/i);
let colourOrWhite = value => isSixDigitHex(value) ? value : '#FFFFFF';
Modules.ColourPreview = function() {
this.start = component => {
this.$input = $(component);
this.$input.closest('.usa-form-group').append(
this.$preview = $('<span class="textbox-colour-preview"></span>')
);
this.$input
.on('input', this.update)
.trigger('input');
};
this.update = () => this.$preview.css(
'background', colourOrWhite(this.$input.val())
);
};
})(window.GOVUK.Modules);

View File

@@ -0,0 +1,15 @@
(function (window) {
"use strict";
function hasConsentFor (cookieCategory, consentCookie) {
if (consentCookie === undefined) { consentCookie = window.GOVUK.getConsentCookie(); }
if (consentCookie === null) { return false; }
if (!(cookieCategory in consentCookie)) { return false; }
return consentCookie[cookieCategory];
}
window.GOVUK.hasConsentFor = hasConsentFor;
})(window);

View File

@@ -0,0 +1,104 @@
window.GOVUK = window.GOVUK || {};
window.GOVUK.Modules = window.GOVUK.Modules || {};
(function (Modules) {
function CookieBanner () { }
CookieBanner.clearOldCookies = function (consent) {
var gaCookies = ['_ga', '_gid'];
// clear old cookie set by our previous JS, set on the www domain
if (window.GOVUK.cookie('seen_cookie_message')) {
document.cookie = 'seen_cookie_message=;expires=' + new Date().toGMTString() + ';path=/';
}
if (consent === null) {
for (var i = 0; i < gaCookies.length; i++) {
if (window.GOVUK.cookie(gaCookies[i])) {
// GA cookies are set on the base domain so need the www stripping
var cookieString = gaCookies[i] + '=;expires=' + new Date().toGMTString() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
document.cookie = cookieString;
}
}
}
};
CookieBanner.prototype.start = function ($module) {
this.$module = $module[0];
this.$module.hideCookieMessage = this.hideCookieMessage.bind(this);
this.$module.showConfirmationMessage = this.showConfirmationMessage.bind(this);
this.$module.setCookieConsent = this.setCookieConsent.bind(this);
this.$module.cookieBanner = document.querySelector('.notify-cookie-banner');
this.$module.cookieBannerConfirmationMessage = this.$module.querySelector('.notify-cookie-banner__confirmation');
this.setupCookieMessage();
};
CookieBanner.prototype.setupCookieMessage = function () {
this.$hideLink = this.$module.querySelector('button[data-hide-cookie-banner]');
if (this.$hideLink) {
this.$hideLink.addEventListener('click', this.$module.hideCookieMessage);
}
this.$acceptCookiesLink = this.$module.querySelector('button[data-accept-cookies=true]');
if (this.$acceptCookiesLink) {
this.$acceptCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(true));
}
this.$rejectCookiesLink = this.$module.querySelector('button[data-accept-cookies=false]');
if (this.$rejectCookiesLink) {
this.$rejectCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(false));
}
this.showCookieMessage();
};
CookieBanner.prototype.showCookieMessage = function () {
// Show the cookie banner if not in the cookie settings page
if (!this.isInCookiesPage()) {
var hasCookiesPolicy = window.GOVUK.cookie('cookies_policy');
if (this.$module && !hasCookiesPolicy) {
this.$module.style.display = 'block';
}
}
};
CookieBanner.prototype.hideCookieMessage = function (event) {
if (this.$module) {
this.$module.style.display = 'none';
}
if (event.target) {
event.preventDefault();
}
};
CookieBanner.prototype.setCookieConsent = function (analyticsConsent) {
window.GOVUK.setConsentCookie({ 'analytics': analyticsConsent });
this.$module.showConfirmationMessage(analyticsConsent);
this.$module.cookieBannerConfirmationMessage.focus();
if (analyticsConsent) { window.GOVUK.initAnalytics(); }
};
CookieBanner.prototype.showConfirmationMessage = function (analyticsConsent) {
var messagePrefix = analyticsConsent ? 'Youve accepted analytics cookies.' : 'You told us not to use analytics cookies.';
this.$cookieBannerMainContent = document.querySelector('.notify-cookie-banner__wrapper');
this.$cookieBannerConfirmationMessage = document.querySelector('.notify-cookie-banner__confirmation-message');
this.$cookieBannerConfirmationMessage.insertAdjacentText('afterbegin', messagePrefix);
this.$cookieBannerMainContent.style.display = 'none';
this.$module.cookieBannerConfirmationMessage.style.display = 'block';
};
CookieBanner.prototype.isInCookiesPage = function () {
return window.location.pathname === '/cookies';
};
Modules.CookieBanner = CookieBanner;
})(window.GOVUK.Modules);

View File

@@ -80,14 +80,14 @@
$('.copy-to-clipboard__value', component)[0], () =>
$component
.html(states.valueCopied(stateOptions))
.find('.usa-button').focus()
.find('.govuk-button').focus()
)
)
.on(
'click', '.copy-to-clipboard__button--show', () =>
$component
.html(states.valueVisible(stateOptions))
.find('.usa-button').focus()
.find('.govuk-button').focus()
);
if ('stickAtBottomWhenScrolling' in GOVUK) {

View File

@@ -1,10 +0,0 @@
(function (window) {
"use strict";
// Show the current year
const currentYearElement = document.getElementById("current-year");
if (currentYearElement) {
currentYearElement.textContent = new Date().getFullYear();
}
})(window);

View File

@@ -1,69 +1,34 @@
function announceUploadStatusFromElement() {
const srRegion = document.getElementById('upload-status-live');
const success = document.getElementById('upload-success');
const error = document.getElementById('upload-error');
if (!srRegion) return;
const message = error?.textContent || success?.textContent;
if (message) {
srRegion.textContent = '';
setTimeout(() => {
srRegion.textContent = message + '\u00A0'; // add a non-breaking space
srRegion.focus(); // Optional
}, 300);
}
}
// Exported for use in tests
function initUploadStatusAnnouncer() {
document.addEventListener('DOMContentLoaded', () => {
announceUploadStatusFromElement();
});
}
(function(Modules) {
"use strict";
Modules.FileUpload = function() {
this.submit = () => this.$form.trigger('submit');
this.showCancelButton = () => {
$('.file-upload-button', this.$form).replaceWith(`
<button class='usa-button uploading-button' aria-disabled="true" tabindex="0">
Uploading<span class="dot-anim" aria-hidden="true"></span>
</button>
`);
};
this.showCancelButton = () => $('.file-upload-button', this.$form).replaceWith(`
<a href="" class='usa-button usa-button--secondary'>Cancel upload</a>
`);
this.start = function(component) {
this.$form = $(component);
this.$form.on('click', '[data-module="upload-trigger"]', function () {
const inputId = $(this).data('file-input-id');
const fileInput = document.getElementById(inputId);
if (fileInput) fileInput.click();
});
// The label gets styled like a button and is used to hide the native file upload control. This is so that
// users see a button that looks like the others on the site.
this.$form.find('label.file-upload-button').addClass('usa-button margin-bottom-1').attr( {role: 'button', tabindex: '0'} );
// Clear the form if the user navigates back to the page
$(window).on("pageshow", () => this.$form[0].reset());
this.$form.on('change', '.file-upload-field', () => {
this.submit();
this.showCancelButton();
});
// Need to put the event on the container, not the input for it to work properly
this.$form.on(
'change', '.file-upload-field',
() => this.submit() && this.showCancelButton()
);
};
};
})(window.GOVUK.Modules);
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
announceUploadStatusFromElement,
initUploadStatusAnnouncer
};
}
if (typeof window !== 'undefined') {
initUploadStatusAnnouncer();
}

View File

@@ -22,6 +22,7 @@
this.$scrollableTable
.on('scroll', this.toggleShadows)
.on('scroll', this.maintainHeight)
.on('focus blur', () => this.$component.toggleClass('js-focus-style'));
if (
window.GOVUK.stickAtBottomWhenScrolling &&
@@ -36,11 +37,11 @@
this.insertShims = () => {
const attributesForFocus = 'role aria-labelledby';
const attributesForFocus = 'role aria-labelledby tabindex';
let captionId = this.$table.find('caption').text().toLowerCase().replace(/[^A-Za-z]+/g, '');
this.$table.find('caption').attr('id', captionId);
this.$table.wrap(`<div class="fullscreen-scrollable-table" role="region" aria-labelledby="${captionId}"/>`);
this.$table.wrap(`<div class="fullscreen-scrollable-table" role="region" aria-labelledby="${captionId}" tabindex="0"/>`);
this.$component
.append(

View File

@@ -0,0 +1,163 @@
// used by the cookie banner component
(function (root) {
'use strict';
window.GOVUK = window.GOVUK || {};
var DEFAULT_COOKIE_CONSENT = {
'analytics': false
};
var COOKIE_CATEGORIES = {
'_ga': 'analytics',
'_gid': 'analytics'
};
/*
Cookie methods
==============
Usage:
Setting a cookie:
GOVUK.cookie('hobnob', 'tasty', { days: 30 });
Reading a cookie:
GOVUK.cookie('hobnob');
Deleting a cookie:
GOVUK.cookie('hobnob', null);
*/
window.GOVUK.cookie = function (name, value, options) {
if (typeof value !== 'undefined') {
if (value === false || value === null) {
return window.GOVUK.setCookie(name, '', { days: -1 });
} else {
// Default expiry date of 30 days
if (typeof options === 'undefined') {
options = { days: 30 };
}
return window.GOVUK.setCookie(name, value, options);
}
} else {
return window.GOVUK.getCookie(name);
}
};
window.GOVUK.getConsentCookie = function () {
var consentCookie = window.GOVUK.cookie('cookies_policy');
var consentCookieObj;
if (consentCookie) {
try {
consentCookieObj = JSON.parse(consentCookie);
} catch (err) {
return null;
}
if (typeof consentCookieObj !== 'object' && consentCookieObj !== null) {
consentCookieObj = JSON.parse(consentCookieObj);
}
} else {
return null;
}
return consentCookieObj;
};
window.GOVUK.setConsentCookie = function (options) {
var cookieConsent = window.GOVUK.getConsentCookie();
if (!cookieConsent) {
cookieConsent = JSON.parse(JSON.stringify(DEFAULT_COOKIE_CONSENT));
}
for (var cookieType in options) {
cookieConsent[cookieType] = options[cookieType];
// Delete cookies of that type if consent being set to false
if (!options[cookieType]) {
for (var cookie in COOKIE_CATEGORIES) {
if (COOKIE_CATEGORIES[cookie] === cookieType) {
window.GOVUK.cookie(cookie, null);
if (window.GOVUK.cookie(cookie)) {
document.cookie = cookie + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/';
}
}
}
}
}
window.GOVUK.setCookie('cookies_policy', JSON.stringify(cookieConsent), { days: 365 });
};
window.GOVUK.checkConsentCookieCategory = function (cookieName, cookieCategory) {
var currentConsentCookie = window.GOVUK.getConsentCookie();
// If the consent cookie doesn't exist, but the cookie is in our known list, return true
if (!currentConsentCookie && COOKIE_CATEGORIES[cookieName]) {
return true;
}
currentConsentCookie = window.GOVUK.getConsentCookie();
// Sometimes currentConsentCookie is malformed in some of the tests, so we need to handle these
try {
return currentConsentCookie[cookieCategory];
} catch (e) {
console.error(e);
return false;
}
};
window.GOVUK.checkConsentCookie = function (cookieName, cookieValue) {
// If we're setting the consent cookie OR deleting a cookie, allow by default
if (cookieName === 'cookies_policy' || (cookieValue === null || cookieValue === false)) {
return true;
}
if (COOKIE_CATEGORIES[cookieName]) {
var cookieCategory = COOKIE_CATEGORIES[cookieName];
return window.GOVUK.checkConsentCookieCategory(cookieName, cookieCategory);
} else {
// Deny the cookie if it is not known to us
return false;
}
};
window.GOVUK.setCookie = function (name, value, options) {
if (window.GOVUK.checkConsentCookie(name, value)) {
if (typeof options === 'undefined') {
options = {};
}
var cookieString = name + '=' + value + '; path=/';
if (options.days) {
var date = new Date();
date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000));
cookieString = cookieString + '; expires=' + date.toGMTString();
}
if (document.location.protocol === 'https:') {
cookieString = cookieString + '; Secure';
}
document.cookie = cookieString;
}
};
window.GOVUK.getCookie = function (name) {
var nameEQ = name + '=';
var cookies = document.cookie.split(';');
for (var i = 0, len = cookies.length; i < len; i++) {
var cookie = cookies[i];
while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length);
}
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null;
};
}(window));

View File

@@ -0,0 +1,24 @@
(function(window) {
"use strict";
window.GOVUK.Modules.Homepage = function() {
this.start = function(component) {
let $component = $(component);
let iterations = 0;
let timeout = null;
$component.on('click', () => {
if (++iterations == 5) {
$component.toggleClass('product-page-intro-wrapper--alternative');
}
clearTimeout(timeout);
timeout = setTimeout(() => iterations = 0, 1500);
});
};
};
})(window);

View File

@@ -17,7 +17,6 @@
this.minEntries = 2;
this.listItemName = this.$wrapper.data('listItemName');
this.getSharedAttributes();
this.getOriginalClasses();
this.getValues();
this.maxEntries = this.entries.length;
@@ -26,33 +25,31 @@
this.bindEvents();
};
ListEntry.optionalAttributes = ['aria-describedby'];
ListEntry.prototype.renderEntry = function(data) {
return `
<div class="list-entry">
<label for="${data.id}" class="usa-label">
<span class="usa-sr-only">${data.listItemName} number </span>${data.number}.
</label>
<input
class="usa-input ${data.classes || ''}"
name="${data.name}"
id="${data.id}"
${data.value ? `value="${data.value}"` : ''}
${data.sharedAttributes}
/>
${data.button ? `
<button type="button" class="usa-button usa-button--unstyled input-list__button--remove">
Remove<span class="usa-sr-only"> ${data.listItemName} number ${data.number}</span>
</button>
` : ''}
</div>
`;
};
ListEntry.prototype.renderAddButton = function(data) {
return `<button type="button" class="usa-button usa-button--outline input-list__button--add margin-top-4">Add another ${data.listItemName} (${data.entriesLeft} remaining)</button>`;
};
ListEntry.prototype.entryTemplate = Hogan.compile(
'<div class="list-entry">' +
'<label for="{{{id}}}" class="govuk-input--numbered__label">' +
'<span class="usa-sr-only">{{listItemName}} number </span>{{number}}.' +
'</label>' +
'<input' +
' name="{{name}}"' +
' id="{{id}}"' +
' {{#value}}value="{{value}}{{/value}}"' +
' {{{sharedAttributes}}}' +
'/>' +
'{{#button}}' +
'<button type="button" class="govuk-button govuk-button--secondary input-list__button--remove">' +
'Remove<span class="usa-sr-only"> {{listItemName}} number {{number}}</span>' +
'</button>' +
'{{/button}}' +
'</div>'
);
ListEntry.prototype.addButtonTemplate = Hogan.compile(
'<button type="button" class="govuk-button govuk-button--secondary input-list__button--add">Add another {{listItemName}} ({{entriesLeft}} remaining)</button>'
);
ListEntry.prototype.getSharedAttributes = function () {
var $inputs = this.$wrapper.find('input'),
generatedAttributes = ['id', 'name', 'value', 'class'],
attributeTemplate = Hogan.compile(' {{name}}="{{value}}"'),
generatedAttributes = ['id', 'name', 'value'],
attributes = [],
attrIdx,
elmAttributes,
@@ -71,7 +68,7 @@
while (attrIdx--) {
// prevent duplicates
if ($.inArray(elmAttrs[attrIdx].name, existingAttributes) === -1) {
attrStr += ` ${elmAttrs[attrIdx].name}="${elmAttrs[attrIdx].value}"`;
attrStr += attributeTemplate.render({ 'name': elmAttrs[attrIdx].name, 'value': elmAttrs[attrIdx].value });
existingAttributes.push(elmAttrs[attrIdx].name);
}
}
@@ -97,20 +94,6 @@
this.sharedAttributes = (attributes.length) ? getAttributesHTML(attributes) : '';
};
ListEntry.prototype.getOriginalClasses = function () {
var $firstInput = this.$wrapper.find('input').first();
if ($firstInput.length) {
var classList = $firstInput.attr('class');
if (classList) {
// Preserve any additional classes from the original input
this.additionalClasses = classList;
} else {
this.additionalClasses = '';
}
} else {
this.additionalClasses = '';
}
};
ListEntry.prototype.getValues = function () {
this.entries = [];
this.$wrapper.find('input').each(function (idx, elm) {
@@ -199,17 +182,16 @@
'name' : this.getId(entryNumber),
'value' : entry,
'listItemName' : this.listItemName,
'sharedAttributes': this.sharedAttributes,
'classes': this.additionalClasses
'sharedAttributes': this.sharedAttributes
};
if (entryNumber > 1) {
dataObj.button = true;
}
this.$wrapper.append(this.renderEntry(dataObj));
this.$wrapper.append(this.entryTemplate.render(dataObj));
}.bind(this));
if (this.entries.length < this.maxEntries) {
this.$wrapper.append(this.renderAddButton({
this.$wrapper.append(this.addButtonTemplate.render({
'listItemName' : this.listItemName,
'entriesLeft' : (this.maxEntries - this.entries.length)
}));

View File

@@ -15,11 +15,11 @@
let query = normalize($searchBox.val());
let results = 0;
let $noResultsMessage = $('.js-live-search-no-results');
$targets.each(function() {
let content = $('.live-search-relevant', this).text() || $(this).text();
let isMatch = normalize(content).indexOf(normalize(query)) > -1;
if ($(this).has(':checked').length) {
$(this).show();
@@ -28,27 +28,17 @@
}
if (query == '') {
$(this).removeClass('js-hidden');
$(this).css('display', '');
results++;
return;
}
let isMatch = normalize(content).includes(normalize(query));
$(this).toggle(isMatch);
if (isMatch) { results++; }
if (isMatch) {
$(this).removeClass('js-hidden');
results++;
} else {
$(this).addClass('js-hidden');
}
});
if (query !== '' && results === 0) {
$noResultsMessage.removeClass('js-hidden');
} else {
$noResultsMessage.addClass('js-hidden');
}
if (state === 'loaded') {
if (query !== '') {
$searchBox.attr('aria-label', $searchLabel.text().trim() + ', ' + resultsSummary(results));

View File

@@ -1,41 +0,0 @@
(function (window) {
// Set the target date (10 days before March 15th, 2024)
const targetDate = new Date("April 16, 2024 00:00:00").getTime();
// Set a const for the container to write a conditional statement and only run if it exists
const countdownContainer = document.getElementById("countdown-container");
// Function to update the countdown display
function updateCountdown() {
if (countdownContainer) {
const now = new Date().getTime();
const difference = targetDate - now;
// Time calculations for days only
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
// Visibility logic
if (days < 0 || days > 10) {
// Hide if more than 10 days away OR if already past the date
countdownContainer.style.display = "none";
} else {
// Show if 10 days or less remaining
countdownContainer.style.display = "block";
document.getElementById("countdown").innerHTML = days + " days ";
}
}
}
// Expose the updateCountdown function to the outside world
window.updateCountdown = updateCountdown;
// Initial display update
updateCountdown();
// Update the countdown every second (inside the IIFE)
setInterval(updateCountdown, 1000);
})(window);

View File

@@ -1,5 +1,14 @@
window.GOVUK.Frontend.initAll();
var consentData = window.GOVUK.getConsentCookie();
window.GOVUK.Modules.CookieBanner.clearOldCookies(consentData);
if (window.GOVUK.hasConsentFor('analytics', consentData)) {
window.GOVUK.initAnalytics();
}
$(() => $("time.timeago").timeago());
var showHideContent = new GOVUK.ShowHideContent();
showHideContent.init();
@@ -7,6 +16,9 @@ $(() => GOVUK.modules.start());
$(() => $('.error-message, .usa-error-message').eq(0).parent('label').next('input').trigger('focus'));
$(() => $('.govuk-header__container').on('click', function() {
$(this).css('border-color', '#005ea5');
}));
// Applies our expanded focus style to the siblings of links when that link is wrapped in a heading.
//

View File

@@ -1,100 +0,0 @@
let activeModal = null;
let lastFocusedElement = null;
function openModal(modalId) {
const wrapper = document.getElementById(modalId);
if (!wrapper) return;
const modal = wrapper.querySelector('.usa-modal, dialog');
if (!modal) return;
lastFocusedElement = document.activeElement;
wrapper.classList.remove('is-hidden');
modal.removeAttribute('aria-hidden');
modal.removeAttribute('inert');
modal.removeAttribute('hidden');
document.body.classList.add('modal-open');
// Set focus to the first focusable element inside modal
const focusTarget = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusTarget) focusTarget.focus();
modal.addEventListener('keydown', function(e) {
if (e.key !== 'Tab') return;
const focusableElements = modal.querySelectorAll(
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
});
activeModal = wrapper;
}
function closeModal() {
if (!activeModal) return;
const modal = activeModal.querySelector('.usa-modal, dialog');
if (modal) {
modal.setAttribute('aria-hidden', 'true');
modal.setAttribute('inert', '');
modal.setAttribute('hidden', '');
}
activeModal.classList.add('is-hidden');
document.body.classList.remove('modal-open');
if (lastFocusedElement) lastFocusedElement.focus();
activeModal = null;
}
function attachModalTriggers() {
document.querySelectorAll('[data-open-modal]').forEach(btn => {
btn.addEventListener('click', () => {
const modalId = btn.getAttribute('data-open-modal');
openModal(modalId);
});
});
document.querySelectorAll('[data-close-modal]').forEach(btn => {
btn.addEventListener('click', () => {
closeModal();
});
});
}
// Escape key closes modal
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && activeModal) {
closeModal();
}
});
// Optional: click outside modal closes it
document.addEventListener('click', (e) => {
if (activeModal && e.target.classList.contains('usa-modal-overlay')) {
closeModal();
}
});
document.addEventListener('DOMContentLoaded', () => {
attachModalTriggers();
});
// ✅ Check if we're in a Node.js environment (for Jest) before using `module.exports`
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = { closeModal, openModal, attachModalTriggers };
}

View File

@@ -1,45 +1,30 @@
(function () {
(function() {
"use strict";
const disableSubmitButtons = function (event) {
const $submitButton = $(this).find(':submit');
let disableSubmitButtons = function(event) {
var $submitButton = $(this).find(':submit');
if ($submitButton.data('clicked') == 'true') {
if ($submitButton.data('clicked') === 'true') {
event.preventDefault();
return;
}
$submitButton.data('clicked', 'true');
// Add dot animation for Send/Schedule/Cancel buttons
const buttonName = $submitButton.attr('name')?.toLowerCase();
if (["send", "schedule", "cancel"].includes(buttonName)) {
$submitButton.prop('disabled', true);
// Inject dot animation span if not already present
if ($submitButton.find('.dot-anim').length === 0) {
$submitButton.append('<span class="dot-anim" aria-hidden="true"></span>');
}
// Disable Cancel button too
const $cancelButton = $('button[name]').filter(function () {
return $(this).attr('name')?.toLowerCase() === 'cancel';
});
$cancelButton.prop('disabled', true);
setTimeout(() => {
renableSubmitButton($submitButton);
}, 10000); // fallback safety
} else {
$submitButton.data('clicked', 'true');
setTimeout(renableSubmitButton($submitButton), 1500);
}
};
const renableSubmitButton = ($submitButton) => () => {
let renableSubmitButton = $submitButton => () => {
$submitButton.data('clicked', '');
$submitButton.prop('disabled', false);
$submitButton.find('.dot-anim').remove(); // clean up if needed
};
$('form').on('submit', disableSubmitButtons);
})();

View File

@@ -0,0 +1,36 @@
(function (global) {
'use strict';
$ = global.jQuery;
let branding_style = $('.usa-radio input[name="branding_style"]:checked');
if (!branding_style.length) { return; }
branding_style = branding_style.val();
const $paneWrapper = $('<div class="govuk-grid-column-full"></div>');
const $form = $('form');
const previewType = $form.data('previewType');
const $previewPane = $(`<iframe src="/_${previewType}?${buildQueryString(['branding_style', branding_style])}" class="branding-preview" scrolling="no"></iframe>`);
function buildQueryString () {
return $.map(arguments, (val, idx) => encodeURI(val[0]) + '=' + encodeURI(val[1])).join('&');
}
function setPreviewPane (e) {
const $target = $(e.target);
if ($target.attr('name') == 'branding_style') {
branding_style = $target.val();
}
$previewPane.attr('src', `/_${previewType}?${buildQueryString(['branding_style', branding_style])}`);
}
$paneWrapper.append($previewPane);
$form.find('.govuk-grid-row').eq(0).prepend($paneWrapper);
$form.attr('action', location.pathname.replace(new RegExp(`set-${previewType}-branding$`), `preview-${previewType}-branding`));
$form.find('button[type="submit"]').text('Save');
$('fieldset').on('change', 'input[name="branding_style"]', setPreviewPane);
})(window);

View File

@@ -3,70 +3,65 @@
"use strict";
var Modules = global.GOVUK.Modules;
var Hogan = global.Hogan;
// Template functions for rendering component states
let renderStates = {
'initial': function(data) {
return `
${data.showNowAsDefault ? `
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="${data.name}-0" name="${data.name}" type="radio" value="">
<label class="usa-radio__label" for="${data.name}-0">Now</label>
</div>
// Object holding all the states for the component's HTML
let states = {
'initial': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
` : ''}
<div class="radio-select__column margin-y-2">
${data.categories.map(category =>
`<input type='button' class='usa-button usa-button--outline radio-select__button--category' aria-expanded="false" value='${category}' />`
).join('')}
</div>
`;
},
'choose': function(data) {
return `
${data.showNowAsDefault ? `
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="${data.name}-0" name="${data.name}" type="radio" value="">
<label class="usa-radio__label" for="${data.name}-0">Now</label>
</div>
{{/showNowAsDefault}}
<div class="radio-select__column margin-y-2">
{{#categories}}
<input type='button' class='usa-button usa-button--outline radio-select__button--category' aria-expanded="false" value='{{.}}' />
{{/categories}}
</div>
`),
'choose': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
` : ''}
<div class="radio-select__column margin-y-2">
${data.choices.map(choice => `
<div class="usa-radio js-option">
<input class="usa-radio__input" type="radio" value="${choice.value}" id="${choice.id}" name="${data.name}" />
<label class="usa-radio__label" for="${choice.id}">${choice.label}</label>
</div>
`).join('')}
<input type='button' class='usa-button usa-button--outline radio-select__button--done margin-top-4' aria-expanded='true' value='Back to select a new time' />
</div>
`;
},
'chosen': function(data) {
return `
${data.showNowAsDefault ? `
<div class="radio-select__column margin-y-2">
<div class="usa-radio">
<input class="usa-radio__input" id="${data.name}-0" name="${data.name}" type="radio" value="">
<label class="usa-radio__label" for="${data.name}-0">Now</label>
</div>
{{/showNowAsDefault}}
<div class="radio-select__column margin-y-2">
{{#choices}}
<div class="usa-radio js-option">
<input class="usa-radio__input" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
</div>
` : ''}
{{/choices}}
<input type='button' class='usa-button usa-button--outline radio-select__button--done' aria-expanded='true' value='Done' />
</div>
`),
'chosen': Hogan.compile(`
{{#showNowAsDefault}}
<div class="radio-select__column margin-y-2">
${data.choices.map(choice => `
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" type="radio" value="${choice.value}" id="${choice.id}" name="${data.name}" />
<label class="usa-radio__label" for="${choice.id}">${choice.label}</label>
</div>
`).join('')}
<div class="usa-radio">
<input class="usa-radio__input" id="{{name}}-0" name="{{name}}" type="radio" value="">
<label class="usa-radio__label" for="{{name}}-0">Now</label>
</div>
</div>
<div class="radio-select__column margin-y-2">
<input type='button' class='usa-button usa-button--outline radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
</div>
`;
}
{{/showNowAsDefault}}
<div class="radio-select__column margin-y-2">
{{#choices}}
<div class="usa-radio">
<input class="usa-radio__input" checked="checked" type="radio" value="{{value}}" id="{{id}}" name="{{name}}" />
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
</div>
{{/choices}}
</div>
<div class="radio-select__column margin-y-2">
<input type='button' class='usa-button usa-button--outline radio-select__button--reset' aria-expanded='false' value='Choose a different time' />
</div>
`)
};
let shiftFocus = function(elementToFocus, component) {
@@ -85,7 +80,7 @@
let $component = $(component);
let render = (state, data) => {
$component.html(renderStates[state](data));
$component.html(states[state].render(data));
};
// store array of all options in component
let choices = $('label', $component).toArray().map(function(element) {

View File

@@ -1,57 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const sidenavItems = document.querySelectorAll('.usa-sidenav__item > .parent-link');
let lastPath = window.location.pathname;
let debounceTimeout = null;
sidenavItems.forEach((link) => {
const parentItem = link.parentElement;
const sublist = parentItem.querySelector('.usa-sidenav__sublist');
const targetHref = link.getAttribute('href');
// initialize the menu to open the correct submenu based on the current route
if (window.location.pathname.startsWith(targetHref)) {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
}
link.addEventListener('click', (event) => {
const currentPath = window.location.pathname;
// prevent default behavior only if navigating to the same route
if (currentPath === targetHref) {
event.preventDefault();
return;
}
if (sublist && !parentItem.classList.contains('open')) {
// debounce the menu update to avoid flickering
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
}, 50);
}
});
});
// handle browser back/forward navigation
window.addEventListener('popstate', () => {
const currentPath = window.location.pathname;
// sync menu state
sidenavItems.forEach((link) => {
const parentItem = link.parentElement;
const targetHref = link.getAttribute('href');
if (currentPath.startsWith(targetHref)) {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
} else {
parentItem.classList.remove('open');
link.setAttribute('aria-expanded', 'false');
}
});
lastPath = currentPath;
});
});

View File

@@ -1,83 +0,0 @@
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
document.addEventListener('DOMContentLoaded', function () {
const isJobPage = window.location.pathname.includes('/jobs/');
if (!isJobPage) return;
const jobEl = document.querySelector('[data-job-id]');
const jobId = jobEl?.dataset?.jobId;
const featureEnabled = jobEl?.dataset?.feature === 'true';
const apiHost = jobEl?.dataset?.host;
if (!jobId) return;
if (featureEnabled) {
const socket = io(apiHost);
socket.on('connect_error', (err) => {
console.error('Socket connect_error:', err);
});
socket.on('error', (err) => {
console.error('Socket error:', err);
});
socket.on('connect', () => {
socket.emit('join', { room: `job-${jobId}` });
});
window.addEventListener('beforeunload', () => {
socket.emit('leave', { room: `job-${jobId}` });
});
const debouncedUpdate = debounce((data) => {
updateAllJobSections();
}, 1000);
socket.on('job_updated', (data) => {
if (data.job_id !== jobId) return;
debouncedUpdate(data);
});
}
function updateAllJobSections() {
const resourceEl = document.querySelector('[data-socket-update="status"]');
const url = resourceEl?.dataset?.resource;
if (!url) {
console.warn('No resource URL found for job updates');
return;
}
fetch(url)
.then((res) => res.json())
.then(({ status, counts, notifications }) => {
const sections = {
status: document.querySelector('[data-socket-update="status"]'),
counts: document.querySelector('[data-socket-update="counts"]'),
notifications: document.querySelector(
'[data-socket-update="notifications"]'
),
};
if (status && sections.status) {
sections.status.innerHTML = status;
}
if (counts && sections.counts) {
sections.counts.innerHTML = counts;
}
if (notifications && sections.notifications) {
sections.notifications.innerHTML = notifications;
}
})
.catch((err) => {
console.error('Error fetching job update partials:', err);
});
}
});

View File

@@ -197,11 +197,6 @@
if (event.currentTarget.value === 'add-new-template' && this.$singleNotificationChannel) {
window.location = "/services/" + this.$singleChannelService + "/templates/add-" + this.$singleNotificationChannel;
} else {
if (this.currentState === 'add-new-template') {
this.$form.find('input[type=checkbox]').prop('checked', false);
this.selectionStatus.update({ total: 0, templates: 0, folders: 0 });
}
if (this.stateChanged()) {
this.render();
}
@@ -291,29 +286,7 @@
mode = 'dialog';
}
if (this.currentState === 'add-new-template') {
this.$form.find('.template-list-item').addClass('js-hidden');
$('.live-search').addClass('js-hidden');
$('#breadcrumb-template-folders').addClass('js-hidden');
$('#template-list').addClass('js-hidden');
this.$form.find('input[type=checkbox]').prop('checked', false);
this.selectionStatus.update({ total: 0, templates: 0, folders: 0 });
$('#page-title').text('New Template');
$('#page-description').text('Every message starts with a template. Choose to start with a blank template or copy an existing template.');
document.title = 'New Templates';
} else {
this.$form.find('.template-list-item').removeClass('js-hidden');
$('.live-search').removeClass('js-hidden');
$('#breadcrumb-template-folders').removeClass('js-hidden');
$('#template-list').removeClass('js-hidden');
$('#page-title').text('Select or create a template');
$('#page-description').text('Every message starts with a template. To send, choose or create a template.');
document.title = 'Select or create a template';
}
if (currentStateObj && 'setFocus' in currentStateObj) {
if (currentStateObj && ('setFocus' in currentStateObj)) {
scrollTop = $(window).scrollTop();
currentStateObj.setFocus();
$(window).scrollTop(scrollTop);

View File

@@ -1,150 +0,0 @@
(function (window) {
function createTotalMessagesChart() {
var chartContainer = document.getElementById('totalMessageChartContainer');
if (!chartContainer) return;
var chartTitle = document.getElementById('chartTitle').textContent;
// Access data attributes from the HTML
var messagesSent = parseInt(chartContainer.getAttribute('data-messages-sent'));
var messagesRemaining = parseInt(chartContainer.getAttribute('data-messages-remaining'));
var totalMessages = messagesSent + messagesRemaining;
// Update the message below the chart
document.getElementById('message').innerText = `${messagesSent.toLocaleString()} sent / ${messagesRemaining.toLocaleString()} remaining`;
// Calculate minimum width for "Messages Sent" as 1% of the total chart width
var minSentPercentage = (messagesSent === 0) ? 0 : 0.02;
var minSentValue = totalMessages * minSentPercentage;
var displaySent = Math.max(messagesSent, minSentValue);
var displayRemaining = totalMessages - displaySent;
var svg = d3.select("#totalMessageChart");
var width = chartContainer.clientWidth;
var height = 48;
// Ensure the width is set correctly
if (width === 0) {
console.error('Chart container width is 0, cannot set SVG width.');
return;
}
svg.attr("width", width).attr("height", height);
var x = d3.scaleLinear()
.domain([0, totalMessages])
.range([0, width]);
// Create tooltip dynamically
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip");
// Create the initial bars
var sentBar = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", height)
.attr("fill", '#0076d6')
.attr("width", 0) // Start with width 0 for animation
.on('mouseover', function(event) {
tooltip.style('display', 'block')
.html(`Messages Sent: ${messagesSent.toLocaleString()}`);
})
.on('mousemove', function(event) {
tooltip.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY - 20}px`);
})
.on('mouseout', function() {
tooltip.style('display', 'none');
});
var remainingBar = svg.append("rect")
.attr("x", 0) // Initially set to 0, will be updated during animation
.attr("y", 0)
.attr("height", height)
.attr("fill", '#C7CACE')
.attr("width", 0) // Start with width 0 for animation
.on('mouseover', function(event) {
tooltip.style('display', 'block')
.html(`Remaining: ${messagesRemaining.toLocaleString()}`);
})
.on('mousemove', function(event) {
tooltip.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY - 20}px`);
})
.on('mouseout', function() {
tooltip.style('display', 'none');
});
// Animate the bars together as a single cohesive line
svg.transition()
.duration(1000) // Total animation duration
.attr("width", width)
.tween("resize", function() {
var interpolator = d3.interpolate(0, width);
return function(t) {
var newWidth = interpolator(t);
var sentWidth = x(displaySent) / width * newWidth;
var remainingWidth = x(displayRemaining) / width * newWidth;
sentBar.attr("width", sentWidth);
remainingBar.attr("x", sentWidth).attr("width", remainingWidth);
};
});
// Create and populate the accessible table
var tableContainer = document.getElementById('totalMessageTable');
var table = document.createElement('table');
table.className = 'usa-sr-only usa-table';
var caption = document.createElement('caption');
caption.textContent = chartTitle;
table.appendChild(caption);
var thead = document.createElement('thead'); // Ensure thead is created
var theadRow = document.createElement('tr');
var thMessagesSent = document.createElement('th');
thMessagesSent.textContent = 'Messages Sent'; // First column header
var thRemaining = document.createElement('th');
thRemaining.textContent = 'Remaining'; // Second column header
theadRow.appendChild(thMessagesSent);
theadRow.appendChild(thRemaining);
thead.appendChild(theadRow); // Append theadRow to the thead
table.appendChild(thead);
var tbody = document.createElement('tbody');
var tbodyRow = document.createElement('tr');
var tdMessagesSent = document.createElement('td');
tdMessagesSent.textContent = messagesSent.toLocaleString(); // Value for Messages Sent
var tdRemaining = document.createElement('td');
tdRemaining.textContent = messagesRemaining.toLocaleString(); // Value for Remaining
tbodyRow.appendChild(tdMessagesSent);
tbodyRow.appendChild(tdRemaining);
tbody.appendChild(tbodyRow);
table.appendChild(tbody);
tableContainer.appendChild(table);
table.appendChild(tbody);
tableContainer.appendChild(table);
// Ensure the chart resizes correctly on window resize
window.addEventListener('resize', function () {
width = chartContainer.clientWidth;
x.range([0, width]);
svg.attr("width", width);
sentBar.attr("width", x(displaySent));
remainingBar.attr("x", x(displaySent)).attr("width", x(displayRemaining));
});
}
// Initialize total messages chart if the container exists
document.addEventListener('DOMContentLoaded', function() {
createTotalMessagesChart();
});
// Export function for testing
window.createTotalMessagesChart = createTotalMessagesChart;
})(window);

View File

@@ -1,117 +0,0 @@
function showError(input, errorElement, message) {
errorElement.textContent = ""; // Clear existing message
errorElement.style.display = "block";
// Small delay to ensure screen readers pick up the change
setTimeout(() => {
errorElement.textContent = message;
}, 10);
if (input.type !== "radio" && input.type !== "checkbox") {
input.classList.add("usa-input--error");
}
input.setAttribute("aria-describedby", errorElement.id);
}
function hideError(input, errorElement) {
errorElement.style.display = "none";
if (input.type !== "radio" && input.type !== "checkbox") {
input.classList.remove("usa-input--error");
}
input.removeAttribute("aria-describedby");
}
function getFieldLabel(input) {
const label = document.querySelector(`label[for="${input.id}"]`);
return label ? label.textContent.trim() : "This field";
}
// Attach validation logic to forms
function attachValidation() {
const forms = document.querySelectorAll('form[data-force-focus="True"]');
forms.forEach((form) => {
const inputs = form.querySelectorAll("input, textarea, select");
form.addEventListener("submit", function (event) {
let isValid = true;
let firstInvalidInput = null;
const validatedRadioNames = new Set();
inputs.forEach((input) => {
if (input.type === "hidden") return;
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
let errorElement = document.getElementById(errorId);
if (!errorElement) {
errorElement = document.createElement("span");
errorElement.id = errorId;
errorElement.classList.add("usa-error-message");
errorElement.setAttribute("aria-live", "polite");
errorElement.style.display = "none";
if (input.type === "radio") {
const group = form.querySelectorAll(`input[name="${input.name}"]`);
const lastRadio = group[group.length - 1];
lastRadio.parentElement.insertAdjacentElement("afterend", errorElement);
} else {
input.insertAdjacentElement("afterend", errorElement);
}
}
if (input.type === "radio") {
if (validatedRadioNames.has(input.name)) return;
validatedRadioNames.add(input.name);
const radioGroup = form.querySelectorAll(`input[name="${input.name}"]`);
const isChecked = Array.from(radioGroup).some(radio => radio.checked);
if (!isChecked) {
showError(input, errorElement, `Error: A selection must be made.`);
isValid = false;
if (!firstInvalidInput) {
firstInvalidInput = input;
}
}
} else if (input.value.trim() === "") {
showError(input, errorElement, `Error: ${getFieldLabel(input)} is required.`);
isValid = false;
if (!firstInvalidInput) {
firstInvalidInput = input;
}
}
});
if (!isValid) {
event.preventDefault();
if (firstInvalidInput) firstInvalidInput.focus();
}
});
inputs.forEach((input) => {
if (input.type === "hidden") return;
input.addEventListener("input", function () {
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
const errorElement = document.getElementById(errorId);
if (errorElement && input.value.trim() !== "") {
hideError(input, errorElement);
}
});
if (input.type === "radio") {
input.addEventListener("change", function () {
const errorElement = document.getElementById(`${input.name}-error`);
if (errorElement) {
hideError(input, errorElement);
}
});
}
});
});
}
// Automatically attach validation only in the browser
if (typeof window !== "undefined") {
document.addEventListener("DOMContentLoaded", attachValidation);
}
// ✅ Check if we're in a Node.js environment (for Jest) before using `module.exports`
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = { showError, hideError, getFieldLabel, attachValidation };
}

View File

@@ -1,8 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
if (window.uswds && typeof window.uswds.init === 'function') {
console.log("Calling USWDS init");
window.uswds.init();
} else {
console.error("USWDS not found or init is not a function");
}
});

View File

@@ -1,4 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.cookie = `timezone=${timeZone}; path=/`;
})

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More