Compare commits
4 Commits
update-rea
...
ADMIN-96_E
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
363ea9e744 | ||
|
|
59419706be | ||
|
|
8d6f62cde1 | ||
|
|
e8aa3158a5 |
648
.ds.baseline
@@ -1,648 +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/actions/deploy-proxy/action.yml": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
"filename": ".github/actions/deploy-proxy/action.yml",
|
||||
"hashed_secret": "a6c13f5da3788e8d654cd24001dc79a238723248",
|
||||
"is_verified": false,
|
||||
"line_number": 18,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
".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": 120,
|
||||
"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": 388,
|
||||
"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": 33,
|
||||
"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": 631,
|
||||
"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": 122,
|
||||
"is_secret": false
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/app/main/views/test_register.py",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 199,
|
||||
"is_secret": false
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/app/main/views/test_register.py",
|
||||
"hashed_secret": "bb5b7caa27d005d38039e3797c3ddb9bcd22c3c8",
|
||||
"is_verified": false,
|
||||
"line_number": 272,
|
||||
"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": 97,
|
||||
"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": 55,
|
||||
"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": 3266,
|
||||
"is_secret": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-05-12T16:50:20Z"
|
||||
}
|
||||
71
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
@@ -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
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
87
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
@@ -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
|
||||
82
.github/ISSUE_TEMPLATE/manual_qa_template.yml
vendored
@@ -1,82 +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 page
|
||||
- label: 2FA code is sent promptly
|
||||
- label: 2FA code input screen appears as expected
|
||||
- label: After code is entered, 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 person’s 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: Hitting “Sign Out” takes you back to the homepage and logs you out
|
||||
|
||||
18
.github/actions/deploy-proxy/action.yml
vendored
@@ -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
|
||||
@@ -15,23 +12,10 @@ inputs:
|
||||
default: https://github.com/GSA-TTS/cg-egress-proxy.git
|
||||
proxy_version:
|
||||
description: git ref to be deployed
|
||||
default: 1500c67157c1a7a6fbbda7a2de172b3d0a67e703
|
||||
default: main
|
||||
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 }}
|
||||
|
||||
11
.github/actions/setup-project/action.yml
vendored
@@ -9,16 +9,13 @@ runs:
|
||||
sudo apt-get update \
|
||||
&& sudo apt-get install -y --no-install-recommends \
|
||||
libcurl4-openssl-dev
|
||||
- name: Set up Python 3.12.3
|
||||
uses: actions/setup-python@v4
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.12.3"
|
||||
python-version: "3.9"
|
||||
- name: Install poetry
|
||||
shell: bash
|
||||
run: pip install poetry==1.8.5
|
||||
- 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
|
||||
|
||||
18
.github/dependabot.yml
vendored
@@ -3,22 +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
|
||||
schedule:
|
||||
interval: "daily"
|
||||
assignees:
|
||||
- "A-Shumway42"
|
||||
reviewers:
|
||||
- "A-Shumway42"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: "daily"
|
||||
versioning-strategy: increase
|
||||
assignees:
|
||||
- "alexjanousekGSA"
|
||||
reviewers:
|
||||
- "alexjanousekGSA"
|
||||
interval: 'daily'
|
||||
|
||||
|
||||
29
.github/pull_request_template.md
vendored
@@ -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
|
||||
138
.github/workflows/checks.yml
vendored
@@ -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,23 +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 --without-hashes --format=requirements.txt > requirements.txt
|
||||
- uses: pypa/gh-action-pip-audit@v1.1.0
|
||||
- uses: pypa/gh-action-pip-audit@v1.0.6
|
||||
with:
|
||||
inputs: requirements.txt
|
||||
ignore-vulns: |
|
||||
PYSEC-2024-60
|
||||
PYSEC-2022-43162
|
||||
- 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
|
||||
@@ -160,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 &
|
||||
|
||||
95
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||
12
.github/workflows/daily_checks.yml
vendored
@@ -16,17 +16,17 @@ 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 --without-hashes --format=requirements.txt > requirements.txt
|
||||
- uses: pypa/gh-action-pip-audit@v1.1.0
|
||||
- uses: pypa/gh-action-pip-audit@v1.0.6
|
||||
with:
|
||||
inputs: requirements.txt
|
||||
- name: Run npm audit
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
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
|
||||
@@ -43,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'
|
||||
|
||||
64
.github/workflows/deploy-demo.yml
vendored
@@ -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 }}
|
||||
@@ -45,56 +47,38 @@ jobs:
|
||||
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"
|
||||
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"
|
||||
--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
|
||||
|
||||
64
.github/workflows/deploy-prod.yml
vendored
@@ -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 }}
|
||||
@@ -45,56 +47,38 @@ jobs:
|
||||
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"
|
||||
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"
|
||||
--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
|
||||
|
||||
148
.github/workflows/deploy.yml
vendored
@@ -17,94 +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 --without-hashes --format=requirements.txt > 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"
|
||||
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"
|
||||
--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
|
||||
|
||||
30
.github/workflows/drift.yml
vendored
@@ -13,15 +13,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@v3
|
||||
|
||||
- name: Check for drift
|
||||
uses: dflook/terraform-check@v1
|
||||
@@ -39,18 +31,10 @@ 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:
|
||||
@@ -67,18 +51,10 @@ 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:
|
||||
|
||||
12
.github/workflows/terraform-demo.yml
vendored
@@ -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:
|
||||
|
||||
12
.github/workflows/terraform-production.yml
vendored
@@ -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:
|
||||
|
||||
12
.github/workflows/terraform-staging.yml
vendored
@@ -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:
|
||||
|
||||
21
.gitignore
vendored
@@ -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,10 @@ app/templates/vendor
|
||||
secrets.auto.tfvars
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
requirements.txt
|
||||
terraform/sandbox/requirements.txt
|
||||
|
||||
# Playwright
|
||||
playwright/
|
||||
|
||||
# Pyenv
|
||||
# Pyenv/Nodenv
|
||||
.python-version
|
||||
|
||||
# Nodenv
|
||||
.node-version
|
||||
|
||||
# BackstopJS
|
||||
backstop_data/bitmaps_test/
|
||||
backstop_data/html_report/
|
||||
backstop_data/engine_scripts/playwright/
|
||||
|
||||
5
.pa11yci
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"defaults": {
|
||||
"standard": "WCAG2AA",
|
||||
"runners": ["htmlcs"],
|
||||
"chromeLaunchConfig": {
|
||||
"executablePath": "/usr/bin/google-chrome"
|
||||
},
|
||||
"runners": ["htmlcs"],
|
||||
"concurrency": 1,
|
||||
"hideElements": [
|
||||
"nav > ol a",
|
||||
|
||||
@@ -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']
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
3
.profile
@@ -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"
|
||||
|
||||
62
Makefile
@@ -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,27 +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 app
|
||||
make generate-version-file
|
||||
poetry lock --no-update
|
||||
poetry install --sync --no-root
|
||||
bootstrap: generate-version-file ## Set up everything to run the app
|
||||
poetry install --sync
|
||||
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 lock --no-update
|
||||
poetry install --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
|
||||
@@ -54,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}'
|
||||
@@ -67,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
|
||||
@@ -76,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 ***"
|
||||
@@ -96,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
|
||||
@@ -127,6 +97,13 @@ py-lock: ## Syncs dependencies and updates lock file without performing recursiv
|
||||
poetry lock --no-update
|
||||
poetry install --sync
|
||||
|
||||
.PHONY: update-utils
|
||||
update-utils: ## Forces Poetry to pull the latest changes from the notifications-utils repo; requires that you commit the changes to poetry.lock!
|
||||
poetry update notifications-utils
|
||||
@echo
|
||||
@echo !!! PLEASE MAKE SURE TO COMMIT AND PUSH THE UPDATED poetry.lock FILE !!!
|
||||
@echo
|
||||
|
||||
.PHONY: freeze-requirements
|
||||
freeze-requirements: ## create static requirements.txt
|
||||
poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
@@ -176,8 +153,3 @@ upload-static:
|
||||
# @cf map-route notify-admin ${DNS_NAME} --hostname www
|
||||
# @cf unmap-route notify-admin-failwhale ${DNS_NAME} --hostname www
|
||||
# @echo "Failwhale is disabled"
|
||||
|
||||
.PHONY: test-single
|
||||
test-single: export NEW_RELIC_ENVIRONMENT=test
|
||||
test-single: ## Run a single test file
|
||||
poetry run pytest $(TEST_FILE)
|
||||
|
||||
502
README.md
@@ -1,10 +1,6 @@
|
||||

|
||||
# 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,410 +9,89 @@ 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 priviliges 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).
|
||||
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
|
||||
- [tfenv](https://github.com/tfutils/tfenv) - 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 `tfenv` to manage
|
||||
Terraform installations. This is great, but you still need to install Terraform
|
||||
itself, which can be done with this command:
|
||||
|
||||
```sh
|
||||
tfenv install latest:^1.4.0
|
||||
```
|
||||
|
||||
_NOTE: This project currently uses the latest `1.4.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.2
|
||||
```
|
||||
|
||||
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.2` is what is installed on your
|
||||
machine):
|
||||
|
||||
```sh
|
||||
cd notifications-admin
|
||||
pyenv virtualenv 3.12.2 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.2 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`._
|
||||
|
||||
#### 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
|
||||
### 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
|
||||
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
|
||||
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
|
||||
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
|
||||
@@ -427,20 +102,22 @@ 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
|
||||
### Keeping the notification-utils dependency up-to-date
|
||||
|
||||
### Python Installation Errors
|
||||
The `notifications-utils` dependency references the other repository we have at
|
||||
https://github.com/GSA/notifications-utils - this dependency requires a bit of
|
||||
extra legwork to ensure it stays up-to-date.
|
||||
|
||||
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.
|
||||
Whenever a PR is merged in the `notifications-utils` repository, we need to make
|
||||
sure the changes are pulled in here and committed to this repository as well.
|
||||
You can do this by going through these steps:
|
||||
|
||||
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.
|
||||
- Make sure your local `main` branch is up-to-date
|
||||
- Create a new branch to work in
|
||||
- Run `make update-utils`
|
||||
- Commit the updated `poetry.lock` file and push the changes
|
||||
- Make a new PR with the change
|
||||
- Have the PR get reviewed and merged
|
||||
|
||||
## To test the application
|
||||
|
||||
@@ -454,13 +131,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.
|
||||
@@ -473,41 +148,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
|
||||
[GSA’s 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 [GSA’s 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.
|
||||
|
||||
We’re a cross-functional team of technologists with specialized experience
|
||||
working across public benefits programs like Medicaid, SNAP, and unemployment
|
||||
insurance.
|
||||
We’re 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)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Contact: mailto:notify-support@gsa.gov
|
||||
Expires: 2035-10-15T23:59:59Z
|
||||
179
app/__init__.py
@@ -1,10 +1,8 @@
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import secrets
|
||||
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 (
|
||||
@@ -23,8 +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,9 +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_to_boolean,
|
||||
format_auth_type,
|
||||
format_billions,
|
||||
@@ -48,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,
|
||||
@@ -85,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,27 +105,24 @@ 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.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.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_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"))
|
||||
|
||||
@@ -134,22 +134,14 @@ navigation = {
|
||||
"main_navigation": MainNavigation(),
|
||||
"header_navigation": HeaderNavigation(),
|
||||
"org_navigation": OrgNavigation(),
|
||||
"secondary_navigation": SecondaryNavigation(),
|
||||
}
|
||||
|
||||
|
||||
def _csp(config):
|
||||
asset_domain = config["ASSET_DOMAIN"]
|
||||
logo_domain = config["LOGO_CDN_DOMAIN"]
|
||||
api_host_name = config["API_HOST_NAME"]
|
||||
|
||||
csp = {
|
||||
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": [
|
||||
@@ -158,79 +150,14 @@ def _csp(config):
|
||||
"'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",
|
||||
],
|
||||
"connect-src": ["'self'", "https://gov-bam.nr-data.net"],
|
||||
"style-src": ["'self'", asset_domain],
|
||||
"img-src": ["'self'", asset_domain, logo_domain],
|
||||
}
|
||||
|
||||
if api_host_name:
|
||||
csp["connect-src"].append(api_host_name)
|
||||
# this is for web socket
|
||||
if api_host_name.startswith("http://"):
|
||||
ws_url = api_host_name.replace("http://", "ws://")
|
||||
csp["connect-src"].append(ws_url)
|
||||
elif api_host_name.startswith("https://"):
|
||||
ws_url = api_host_name.replace("https://", "wss://")
|
||||
csp["connect-src"].append(ws_url)
|
||||
return csp
|
||||
|
||||
|
||||
def create_app(application):
|
||||
@application.after_request
|
||||
def add_csp_header(response):
|
||||
existing_csp = response.headers.get("Content-Security-Policy", "")
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
existing_csp + "; form-action 'self';"
|
||||
)
|
||||
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])
|
||||
@@ -253,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,
|
||||
@@ -262,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,
|
||||
@@ -270,6 +199,7 @@ def create_app(application):
|
||||
user_api_client,
|
||||
# External API clients
|
||||
redis_client,
|
||||
zendesk_client,
|
||||
):
|
||||
client.init_app(application)
|
||||
|
||||
@@ -298,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
|
||||
@@ -372,7 +284,6 @@ def init_app(application):
|
||||
|
||||
@application.context_processor
|
||||
def _attach_current_global_daily_messages():
|
||||
global_limit = 0
|
||||
remaining_global_messages = 0
|
||||
if current_app:
|
||||
if request.view_args:
|
||||
@@ -390,10 +301,7 @@ def init_app(application):
|
||||
remaining_global_messages = global_limit - global_messages_count.get(
|
||||
"count"
|
||||
)
|
||||
return {
|
||||
"global_message_limit": global_limit,
|
||||
"daily_global_messages_remaining": remaining_global_messages,
|
||||
}
|
||||
return {"daily_global_messages_remaining": remaining_global_messages}
|
||||
|
||||
@application.before_request
|
||||
def record_start_time():
|
||||
@@ -411,6 +319,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
|
||||
|
||||
|
||||
@@ -433,23 +342,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():
|
||||
@@ -457,7 +354,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)
|
||||
|
||||
@@ -479,10 +375,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:
|
||||
@@ -644,16 +536,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,
|
||||
@@ -691,7 +580,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)
|
||||
|
||||
@@ -703,10 +591,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()))
|
||||
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 21 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 52 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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"}
|
||||
|
Before Width: | Height: | Size: 101 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1,365 +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;
|
||||
});
|
||||
|
||||
userRows.slice(0, 5).forEach(row => {
|
||||
row.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);
|
||||
27
app/assets/javascripts/autofocus.js
Normal 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);
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
return $(`<div class="selection-footer${stickyClass}">
|
||||
<button
|
||||
class="govuk-button govuk-button--secondary selection-footer__button usa-button usa-button--outline"
|
||||
class="govuk-button govuk-button--secondary selection-footer__button"
|
||||
aria-expanded="${expanded ? 'true' : 'false'}"
|
||||
aria-controls="${this.fieldsetId}">
|
||||
${buttonContent}
|
||||
|
||||
29
app/assets/javascripts/colourPreview.js
Normal 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);
|
||||
15
app/assets/javascripts/consent.js
Normal 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);
|
||||
104
app/assets/javascripts/cookieMessage.js
Normal 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 ? 'You’ve 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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
163
app/assets/javascripts/govuk/cookie-functions.js
Normal 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));
|
||||
|
||||
24
app/assets/javascripts/homepage.js
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
|
||||
36
app/assets/javascripts/previewPane.js
Normal 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);
|
||||
@@ -38,7 +38,7 @@
|
||||
<label class="usa-radio__label" for="{{id}}">{{label}}</label>
|
||||
</div>
|
||||
{{/choices}}
|
||||
<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' />
|
||||
<input type='button' class='usa-button usa-button--outline radio-select__button--done' aria-expanded='true' value='Done' />
|
||||
</div>
|
||||
`),
|
||||
'chosen': Hogan.compile(`
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
@@ -1,75 +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', () => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
document.cookie = `timezone=${timeZone}; path=/`;
|
||||
})
|
||||