From 340d1f3ee4c3ad3c7162aa6c5d505b28c477d406 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 21:24:37 +0000 Subject: [PATCH 001/137] Update dependency @eslint/js to v9.1.1 --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index c2692aeef..4d6607621 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,7 +29,7 @@ "author": "Jamie Curnow and ZoeyVid ", "license": "MIT", "devDependencies": { - "@eslint/js": "9.0.0", + "@eslint/js": "9.1.1", "eslint": "9.0.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", From d4115307537500a2a3cd4595c7f076edc1db53cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 01:25:34 +0000 Subject: [PATCH 002/137] Update dependency eslint to v9.1.0 --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 4d6607621..742b65f6d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,7 +30,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "9.1.1", - "eslint": "9.0.0", + "eslint": "9.1.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", "globals": "15.0.0", From 2db8ab1e2dd983662c94c5811d3a339d226a2209 Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 20 Apr 2024 18:37:37 +0200 Subject: [PATCH 003/137] Update renovate.json Signed-off-by: Zoey --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 0897d9b39..242772694 100644 --- a/renovate.json +++ b/renovate.json @@ -2,7 +2,7 @@ "extends": [ "config:base" ], - "baseBranches": [], + "baseBranches": ["develop", "php"], "includeForks": true, "automerge": false, "branchPrefix": "renovate-deps-update-", From 70352021af2a8a857c7b37591e5ce1775547c50b Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 20 Apr 2024 17:27:10 +0200 Subject: [PATCH 004/137] remove global/frintend and backend folder Signed-off-by: Zoey --- .version | 1 - backend/.gitignore | 8 - backend/.prettierrc | 7 - backend/app.js | 87 - backend/db.js | 28 - backend/doc/api.swagger.json | 1456 ----------------- backend/eslint.config.mjs | 5 - backend/index.js | 49 - backend/internal/access-list.js | 511 ------ backend/internal/audit-log.js | 71 - backend/internal/certificate.js | 1125 ------------- backend/internal/dead-host.js | 450 ----- backend/internal/host.js | 217 --- backend/internal/ip_ranges.js | 150 -- backend/internal/nginx.js | 376 ----- backend/internal/proxy-host.js | 457 ------ backend/internal/redirection-host.js | 450 ----- backend/internal/report.js | 32 - backend/internal/setting.js | 125 -- backend/internal/stream.js | 331 ---- backend/internal/token.js | 155 -- backend/internal/user.js | 482 ------ backend/knexfile.js | 19 - backend/lib/access.js | 300 ---- backend/lib/access/access_lists-create.json | 23 - backend/lib/access/access_lists-delete.json | 23 - backend/lib/access/access_lists-get.json | 23 - backend/lib/access/access_lists-list.json | 23 - backend/lib/access/access_lists-update.json | 23 - backend/lib/access/auditlog-list.json | 7 - backend/lib/access/certificates-create.json | 23 - backend/lib/access/certificates-delete.json | 23 - backend/lib/access/certificates-get.json | 23 - backend/lib/access/certificates-list.json | 23 - backend/lib/access/certificates-update.json | 23 - backend/lib/access/dead_hosts-create.json | 23 - backend/lib/access/dead_hosts-delete.json | 23 - backend/lib/access/dead_hosts-get.json | 23 - backend/lib/access/dead_hosts-list.json | 23 - backend/lib/access/dead_hosts-update.json | 23 - backend/lib/access/permissions.json | 14 - backend/lib/access/proxy_hosts-create.json | 23 - backend/lib/access/proxy_hosts-delete.json | 23 - backend/lib/access/proxy_hosts-get.json | 23 - backend/lib/access/proxy_hosts-list.json | 23 - backend/lib/access/proxy_hosts-update.json | 23 - .../lib/access/redirection_hosts-create.json | 23 - .../lib/access/redirection_hosts-delete.json | 23 - backend/lib/access/redirection_hosts-get.json | 23 - .../lib/access/redirection_hosts-list.json | 23 - .../lib/access/redirection_hosts-update.json | 23 - backend/lib/access/reports-hosts.json | 7 - backend/lib/access/roles.json | 39 - backend/lib/access/settings-get.json | 7 - backend/lib/access/settings-list.json | 7 - backend/lib/access/settings-update.json | 7 - backend/lib/access/streams-create.json | 23 - backend/lib/access/streams-delete.json | 23 - backend/lib/access/streams-get.json | 23 - backend/lib/access/streams-list.json | 23 - backend/lib/access/streams-update.json | 23 - backend/lib/access/users-create.json | 7 - backend/lib/access/users-delete.json | 7 - backend/lib/access/users-get.json | 23 - backend/lib/access/users-list.json | 7 - backend/lib/access/users-loginas.json | 7 - backend/lib/access/users-password.json | 23 - backend/lib/access/users-permissions.json | 7 - backend/lib/access/users-update.json | 23 - backend/lib/certbot.js | 75 - backend/lib/config.js | 186 --- backend/lib/error.js | 98 -- backend/lib/express/cors.js | 36 - backend/lib/express/jwt-decode.js | 15 - backend/lib/express/jwt.js | 13 - backend/lib/express/pagination.js | 53 - backend/lib/express/user-id-from-me.js | 9 - backend/lib/helpers.js | 30 - backend/lib/migrate_template.js | 54 - backend/lib/utils.js | 138 -- backend/lib/validator/api.js | 43 - backend/lib/validator/index.js | 43 - backend/logger.js | 14 - backend/migrate.js | 14 - backend/migrations/20180618015850_initial.js | 205 --- .../migrations/20180929054513_websockets.js | 35 - .../migrations/20181019052346_forward_host.js | 35 - .../20181113041458_http2_support.js | 49 - .../20181213013211_forward_scheme.js | 35 - backend/migrations/20190104035154_disabled.js | 56 - .../20190215115310_customlocations.js | 36 - backend/migrations/20190218060101_hsts.js | 52 - backend/migrations/20190227065017_settings.js | 39 - .../20200410143839_access_list_client.js | 51 - .../20200410143840_access_list_client_fix.js | 35 - .../migrations/20201014143841_pass_auth.js | 42 - .../20210210154702_redirection_scheme.js | 42 - .../20210210154703_redirection_status_code.js | 42 - .../20210423103500_stream_domain.js | 42 - .../20211108145214_regenerate_default_host.js | 51 - backend/models/access_list.js | 86 - backend/models/access_list_auth.js | 54 - backend/models/access_list_client.js | 54 - backend/models/audit-log.js | 52 - backend/models/auth.js | 82 - backend/models/certificate.js | 65 - backend/models/dead_host.js | 72 - backend/models/now_helper.js | 12 - backend/models/proxy_host.js | 84 - backend/models/redirection_host.js | 74 - backend/models/setting.js | 30 - backend/models/stream.js | 55 - backend/models/token.js | 126 -- backend/models/user.js | 52 - backend/models/user_permission.js | 29 - backend/nodemon.json | 7 - backend/package.json | 39 - backend/password-reset.js | 59 - backend/routes/api/audit-log.js | 54 - backend/routes/api/main.js | 51 - backend/routes/api/nginx/access_lists.js | 150 -- backend/routes/api/nginx/certificates.js | 299 ---- backend/routes/api/nginx/dead_hosts.js | 198 --- backend/routes/api/nginx/proxy_hosts.js | 198 --- backend/routes/api/nginx/redirection_hosts.js | 198 --- backend/routes/api/nginx/streams.js | 198 --- backend/routes/api/reports.js | 29 - backend/routes/api/schema.js | 36 - backend/routes/api/settings.js | 97 -- backend/routes/api/tokens.js | 53 - backend/routes/api/users.js | 239 --- backend/schema/definitions.json | 240 --- backend/schema/endpoints/access-lists.json | 236 --- backend/schema/endpoints/certificates.json | 173 -- backend/schema/endpoints/dead-hosts.json | 240 --- backend/schema/endpoints/proxy-hosts.json | 387 ----- .../schema/endpoints/redirection-hosts.json | 305 ---- backend/schema/endpoints/settings.json | 99 -- backend/schema/endpoints/streams.json | 234 --- backend/schema/endpoints/tokens.json | 100 -- backend/schema/endpoints/users.json | 287 ---- backend/schema/examples.json | 23 - backend/schema/index.json | 42 - backend/setup.js | 145 -- backend/sqlite-vaccum.js | 23 - backend/templates/_access.conf | 25 - backend/templates/_brotli.conf | 4 - backend/templates/_certificates.conf | 15 - backend/templates/_forced_tls.conf | 6 - backend/templates/_header_comment.conf | 3 - backend/templates/_hsts.conf | 17 - backend/templates/_listen.conf | 20 - backend/templates/_location.conf | 17 - backend/templates/dead_host.conf | 26 - backend/templates/default.conf | 61 - backend/templates/ip_ranges.conf | 3 - backend/templates/proxy_host.conf | 58 - backend/templates/redirection_host.conf | 28 - backend/templates/stream.conf | 29 - darkmode.css | 247 --- frontend/.gitignore | 4 - frontend/app-images/default-avatar.jpg | Bin 1753 -> 0 bytes .../favicons/android-chrome-192x192.png | Bin 17104 -> 0 bytes .../favicons/android-chrome-512x512.png | Bin 54834 -> 0 bytes .../app-images/favicons/apple-touch-icon.png | Bin 15800 -> 0 bytes .../app-images/favicons/browserconfig.xml | 9 - .../app-images/favicons/favicon-16x16.png | Bin 1220 -> 0 bytes .../app-images/favicons/favicon-32x32.png | Bin 2386 -> 0 bytes frontend/app-images/favicons/favicon.ico | Bin 15086 -> 0 bytes .../app-images/favicons/mstile-150x150.png | Bin 11672 -> 0 bytes .../app-images/favicons/safari-pinned-tab.svg | 1 - frontend/app-images/favicons/site.webmanifest | 19 - frontend/app-images/logo-256.png | Bin 29709 -> 0 bytes .../app-images/logo-text-vertical-grey.png | Bin 15526 -> 0 bytes frontend/fonts/feather | 1 - ...urce-sans-pro-v14-latin-ext_latin-700.woff | Bin 31740 -> 0 bytes ...rce-sans-pro-v14-latin-ext_latin-700.woff2 | Bin 25348 -> 0 bytes ...ans-pro-v14-latin-ext_latin-700italic.woff | Bin 28540 -> 0 bytes ...ns-pro-v14-latin-ext_latin-700italic.woff2 | Bin 22240 -> 0 bytes ...e-sans-pro-v14-latin-ext_latin-italic.woff | Bin 28744 -> 0 bytes ...-sans-pro-v14-latin-ext_latin-italic.woff2 | Bin 22436 -> 0 bytes ...-sans-pro-v14-latin-ext_latin-regular.woff | Bin 32128 -> 0 bytes ...sans-pro-v14-latin-ext_latin-regular.woff2 | Bin 25656 -> 0 bytes frontend/html/index.ejs | 9 - frontend/html/login.ejs | 9 - frontend/html/partials/footer.ejs | 2 - frontend/html/partials/header.ejs | 73 - frontend/images | 1 - frontend/js/app/api.js | 757 --------- frontend/js/app/audit-log/list/item.ejs | 80 - frontend/js/app/audit-log/list/item.js | 32 - frontend/js/app/audit-log/list/main.ejs | 9 - frontend/js/app/audit-log/list/main.js | 27 - frontend/js/app/audit-log/main.ejs | 25 - frontend/js/app/audit-log/main.js | 82 - frontend/js/app/audit-log/meta.ejs | 27 - frontend/js/app/audit-log/meta.js | 7 - frontend/js/app/cache.js | 10 - frontend/js/app/controller.js | 447 ----- frontend/js/app/dashboard/main.ejs | 67 - frontend/js/app/dashboard/main.js | 92 -- frontend/js/app/empty/main.ejs | 11 - frontend/js/app/empty/main.js | 33 - frontend/js/app/error/main.ejs | 7 - frontend/js/app/error/main.js | 27 - frontend/js/app/help/main.ejs | 12 - frontend/js/app/help/main.js | 16 - frontend/js/app/i18n.js | 23 - frontend/js/app/main.js | 155 -- frontend/js/app/nginx/access/delete.ejs | 23 - frontend/js/app/nginx/access/delete.js | 32 - frontend/js/app/nginx/access/form.ejs | 108 -- frontend/js/app/nginx/access/form.js | 153 -- frontend/js/app/nginx/access/form/client.ejs | 13 - frontend/js/app/nginx/access/form/client.js | 7 - frontend/js/app/nginx/access/form/item.ejs | 10 - frontend/js/app/nginx/access/form/item.js | 7 - frontend/js/app/nginx/access/list/item.ejs | 42 - frontend/js/app/nginx/access/list/item.js | 33 - frontend/js/app/nginx/access/list/main.ejs | 14 - frontend/js/app/nginx/access/list/main.js | 32 - frontend/js/app/nginx/access/main.ejs | 28 - frontend/js/app/nginx/access/main.js | 108 -- .../js/app/nginx/certificates-list-item.ejs | 18 - frontend/js/app/nginx/certificates/delete.ejs | 19 - frontend/js/app/nginx/certificates/delete.js | 34 - frontend/js/app/nginx/certificates/form.ejs | 184 --- frontend/js/app/nginx/certificates/form.js | 297 ---- .../js/app/nginx/certificates/list/item.ejs | 54 - .../js/app/nginx/certificates/list/item.js | 58 - .../js/app/nginx/certificates/list/main.ejs | 12 - .../js/app/nginx/certificates/list/main.js | 32 - frontend/js/app/nginx/certificates/main.ejs | 36 - frontend/js/app/nginx/certificates/main.js | 109 -- frontend/js/app/nginx/certificates/renew.ejs | 14 - frontend/js/app/nginx/certificates/renew.js | 31 - frontend/js/app/nginx/certificates/test.ejs | 15 - frontend/js/app/nginx/certificates/test.js | 75 - frontend/js/app/nginx/dead/delete.ejs | 23 - frontend/js/app/nginx/dead/delete.js | 32 - frontend/js/app/nginx/dead/form.ejs | 206 --- frontend/js/app/nginx/dead/form.js | 274 ---- frontend/js/app/nginx/dead/list/item.ejs | 54 - frontend/js/app/nginx/dead/list/item.js | 61 - frontend/js/app/nginx/dead/list/main.ejs | 12 - frontend/js/app/nginx/dead/list/main.js | 32 - frontend/js/app/nginx/dead/main.ejs | 28 - frontend/js/app/nginx/dead/main.js | 108 -- .../js/app/nginx/proxy/access-list-item.ejs | 13 - frontend/js/app/nginx/proxy/delete.ejs | 23 - frontend/js/app/nginx/proxy/delete.js | 32 - frontend/js/app/nginx/proxy/form.ejs | 281 ---- frontend/js/app/nginx/proxy/form.js | 357 ---- frontend/js/app/nginx/proxy/list/item.ejs | 60 - frontend/js/app/nginx/proxy/list/item.js | 61 - frontend/js/app/nginx/proxy/list/main.ejs | 14 - frontend/js/app/nginx/proxy/list/main.js | 32 - frontend/js/app/nginx/proxy/location-item.ejs | 64 - frontend/js/app/nginx/proxy/location.js | 54 - frontend/js/app/nginx/proxy/main.ejs | 28 - frontend/js/app/nginx/proxy/main.js | 108 -- frontend/js/app/nginx/redirection/delete.ejs | 23 - frontend/js/app/nginx/redirection/delete.js | 32 - frontend/js/app/nginx/redirection/form.ejs | 255 --- frontend/js/app/nginx/redirection/form.js | 276 ---- .../js/app/nginx/redirection/list/item.ejs | 63 - .../js/app/nginx/redirection/list/item.js | 61 - .../js/app/nginx/redirection/list/main.ejs | 15 - .../js/app/nginx/redirection/list/main.js | 32 - frontend/js/app/nginx/redirection/main.ejs | 28 - frontend/js/app/nginx/redirection/main.js | 107 -- frontend/js/app/nginx/stream/delete.ejs | 19 - frontend/js/app/nginx/stream/delete.js | 32 - frontend/js/app/nginx/stream/form.ejs | 55 - frontend/js/app/nginx/stream/form.js | 84 - frontend/js/app/nginx/stream/list/item.ejs | 53 - frontend/js/app/nginx/stream/list/item.js | 54 - frontend/js/app/nginx/stream/list/main.ejs | 13 - frontend/js/app/nginx/stream/list/main.js | 32 - frontend/js/app/nginx/stream/main.ejs | 28 - frontend/js/app/nginx/stream/main.js | 108 -- frontend/js/app/router.js | 19 - .../js/app/settings/default-site/main.ejs | 57 - frontend/js/app/settings/default-site/main.js | 69 - frontend/js/app/settings/list/item.ejs | 21 - frontend/js/app/settings/list/item.js | 23 - frontend/js/app/settings/list/main.ejs | 8 - frontend/js/app/settings/list/main.js | 27 - frontend/js/app/settings/main.ejs | 14 - frontend/js/app/settings/main.js | 48 - frontend/js/app/tokens.js | 126 -- frontend/js/app/ui/footer/main.ejs | 18 - frontend/js/app/ui/footer/main.js | 14 - frontend/js/app/ui/header/main.ejs | 34 - frontend/js/app/ui/header/main.js | 67 - frontend/js/app/ui/main.ejs | 21 - frontend/js/app/ui/main.js | 98 -- frontend/js/app/ui/menu/main.ejs | 52 - frontend/js/app/ui/menu/main.js | 39 - frontend/js/app/user/delete.ejs | 19 - frontend/js/app/user/delete.js | 34 - frontend/js/app/user/form.ejs | 58 - frontend/js/app/user/form.js | 108 -- frontend/js/app/user/password.ejs | 31 - frontend/js/app/user/password.js | 69 - frontend/js/app/user/permissions.ejs | 68 - frontend/js/app/user/permissions.js | 95 -- frontend/js/app/users/list/item.ejs | 45 - frontend/js/app/users/list/item.js | 68 - frontend/js/app/users/list/main.ejs | 10 - frontend/js/app/users/list/main.js | 27 - frontend/js/app/users/main.ejs | 26 - frontend/js/app/users/main.js | 78 - frontend/js/i18n/messages.json | 297 ---- frontend/js/index.js | 119 -- frontend/js/lib/helpers.js | 26 - frontend/js/lib/marionette.js | 15 - frontend/js/login.js | 5 - frontend/js/login/main.js | 14 - frontend/js/login/ui/login.ejs | 37 - frontend/js/login/ui/login.js | 42 - frontend/js/models/access-list.js | 25 - frontend/js/models/audit-log.js | 18 - frontend/js/models/certificate.js | 38 - frontend/js/models/dead-host.js | 32 - frontend/js/models/proxy-host-location.js | 35 - frontend/js/models/proxy-host.js | 40 - frontend/js/models/redirection-host.js | 37 - frontend/js/models/setting.js | 22 - frontend/js/models/stream.js | 29 - frontend/js/models/user.js | 54 - frontend/package.json | 46 - frontend/scss/custom.scss | 42 - frontend/scss/fonts.scss | 39 - frontend/scss/selectize.scss | 196 --- frontend/scss/styles.scss | 17 - frontend/scss/tabler-extra.scss | 170 -- frontend/webpack.config.js | 143 -- global/README.md | 19 - global/certbot-dns-plugins.json | 338 ---- 340 files changed, 25752 deletions(-) delete mode 100644 .version delete mode 100644 backend/.gitignore delete mode 100644 backend/.prettierrc delete mode 100644 backend/app.js delete mode 100644 backend/db.js delete mode 100644 backend/doc/api.swagger.json delete mode 100644 backend/eslint.config.mjs delete mode 100755 backend/index.js delete mode 100644 backend/internal/access-list.js delete mode 100644 backend/internal/audit-log.js delete mode 100644 backend/internal/certificate.js delete mode 100644 backend/internal/dead-host.js delete mode 100644 backend/internal/host.js delete mode 100644 backend/internal/ip_ranges.js delete mode 100644 backend/internal/nginx.js delete mode 100644 backend/internal/proxy-host.js delete mode 100644 backend/internal/redirection-host.js delete mode 100644 backend/internal/report.js delete mode 100644 backend/internal/setting.js delete mode 100644 backend/internal/stream.js delete mode 100644 backend/internal/token.js delete mode 100644 backend/internal/user.js delete mode 100644 backend/knexfile.js delete mode 100644 backend/lib/access.js delete mode 100644 backend/lib/access/access_lists-create.json delete mode 100644 backend/lib/access/access_lists-delete.json delete mode 100644 backend/lib/access/access_lists-get.json delete mode 100644 backend/lib/access/access_lists-list.json delete mode 100644 backend/lib/access/access_lists-update.json delete mode 100644 backend/lib/access/auditlog-list.json delete mode 100644 backend/lib/access/certificates-create.json delete mode 100644 backend/lib/access/certificates-delete.json delete mode 100644 backend/lib/access/certificates-get.json delete mode 100644 backend/lib/access/certificates-list.json delete mode 100644 backend/lib/access/certificates-update.json delete mode 100644 backend/lib/access/dead_hosts-create.json delete mode 100644 backend/lib/access/dead_hosts-delete.json delete mode 100644 backend/lib/access/dead_hosts-get.json delete mode 100644 backend/lib/access/dead_hosts-list.json delete mode 100644 backend/lib/access/dead_hosts-update.json delete mode 100644 backend/lib/access/permissions.json delete mode 100644 backend/lib/access/proxy_hosts-create.json delete mode 100644 backend/lib/access/proxy_hosts-delete.json delete mode 100644 backend/lib/access/proxy_hosts-get.json delete mode 100644 backend/lib/access/proxy_hosts-list.json delete mode 100644 backend/lib/access/proxy_hosts-update.json delete mode 100644 backend/lib/access/redirection_hosts-create.json delete mode 100644 backend/lib/access/redirection_hosts-delete.json delete mode 100644 backend/lib/access/redirection_hosts-get.json delete mode 100644 backend/lib/access/redirection_hosts-list.json delete mode 100644 backend/lib/access/redirection_hosts-update.json delete mode 100644 backend/lib/access/reports-hosts.json delete mode 100644 backend/lib/access/roles.json delete mode 100644 backend/lib/access/settings-get.json delete mode 100644 backend/lib/access/settings-list.json delete mode 100644 backend/lib/access/settings-update.json delete mode 100644 backend/lib/access/streams-create.json delete mode 100644 backend/lib/access/streams-delete.json delete mode 100644 backend/lib/access/streams-get.json delete mode 100644 backend/lib/access/streams-list.json delete mode 100644 backend/lib/access/streams-update.json delete mode 100644 backend/lib/access/users-create.json delete mode 100644 backend/lib/access/users-delete.json delete mode 100644 backend/lib/access/users-get.json delete mode 100644 backend/lib/access/users-list.json delete mode 100644 backend/lib/access/users-loginas.json delete mode 100644 backend/lib/access/users-password.json delete mode 100644 backend/lib/access/users-permissions.json delete mode 100644 backend/lib/access/users-update.json delete mode 100644 backend/lib/certbot.js delete mode 100644 backend/lib/config.js delete mode 100644 backend/lib/error.js delete mode 100644 backend/lib/express/cors.js delete mode 100644 backend/lib/express/jwt-decode.js delete mode 100644 backend/lib/express/jwt.js delete mode 100644 backend/lib/express/pagination.js delete mode 100644 backend/lib/express/user-id-from-me.js delete mode 100644 backend/lib/helpers.js delete mode 100644 backend/lib/migrate_template.js delete mode 100644 backend/lib/utils.js delete mode 100644 backend/lib/validator/api.js delete mode 100644 backend/lib/validator/index.js delete mode 100644 backend/logger.js delete mode 100644 backend/migrate.js delete mode 100644 backend/migrations/20180618015850_initial.js delete mode 100644 backend/migrations/20180929054513_websockets.js delete mode 100644 backend/migrations/20181019052346_forward_host.js delete mode 100644 backend/migrations/20181113041458_http2_support.js delete mode 100644 backend/migrations/20181213013211_forward_scheme.js delete mode 100644 backend/migrations/20190104035154_disabled.js delete mode 100644 backend/migrations/20190215115310_customlocations.js delete mode 100644 backend/migrations/20190218060101_hsts.js delete mode 100644 backend/migrations/20190227065017_settings.js delete mode 100644 backend/migrations/20200410143839_access_list_client.js delete mode 100644 backend/migrations/20200410143840_access_list_client_fix.js delete mode 100644 backend/migrations/20201014143841_pass_auth.js delete mode 100644 backend/migrations/20210210154702_redirection_scheme.js delete mode 100644 backend/migrations/20210210154703_redirection_status_code.js delete mode 100644 backend/migrations/20210423103500_stream_domain.js delete mode 100644 backend/migrations/20211108145214_regenerate_default_host.js delete mode 100644 backend/models/access_list.js delete mode 100644 backend/models/access_list_auth.js delete mode 100644 backend/models/access_list_client.js delete mode 100644 backend/models/audit-log.js delete mode 100644 backend/models/auth.js delete mode 100644 backend/models/certificate.js delete mode 100644 backend/models/dead_host.js delete mode 100644 backend/models/now_helper.js delete mode 100644 backend/models/proxy_host.js delete mode 100644 backend/models/redirection_host.js delete mode 100644 backend/models/setting.js delete mode 100644 backend/models/stream.js delete mode 100644 backend/models/token.js delete mode 100644 backend/models/user.js delete mode 100644 backend/models/user_permission.js delete mode 100644 backend/nodemon.json delete mode 100644 backend/package.json delete mode 100755 backend/password-reset.js delete mode 100644 backend/routes/api/audit-log.js delete mode 100644 backend/routes/api/main.js delete mode 100644 backend/routes/api/nginx/access_lists.js delete mode 100644 backend/routes/api/nginx/certificates.js delete mode 100644 backend/routes/api/nginx/dead_hosts.js delete mode 100644 backend/routes/api/nginx/proxy_hosts.js delete mode 100644 backend/routes/api/nginx/redirection_hosts.js delete mode 100644 backend/routes/api/nginx/streams.js delete mode 100644 backend/routes/api/reports.js delete mode 100644 backend/routes/api/schema.js delete mode 100644 backend/routes/api/settings.js delete mode 100644 backend/routes/api/tokens.js delete mode 100644 backend/routes/api/users.js delete mode 100644 backend/schema/definitions.json delete mode 100644 backend/schema/endpoints/access-lists.json delete mode 100644 backend/schema/endpoints/certificates.json delete mode 100644 backend/schema/endpoints/dead-hosts.json delete mode 100644 backend/schema/endpoints/proxy-hosts.json delete mode 100644 backend/schema/endpoints/redirection-hosts.json delete mode 100644 backend/schema/endpoints/settings.json delete mode 100644 backend/schema/endpoints/streams.json delete mode 100644 backend/schema/endpoints/tokens.json delete mode 100644 backend/schema/endpoints/users.json delete mode 100644 backend/schema/examples.json delete mode 100644 backend/schema/index.json delete mode 100644 backend/setup.js delete mode 100755 backend/sqlite-vaccum.js delete mode 100644 backend/templates/_access.conf delete mode 100644 backend/templates/_brotli.conf delete mode 100644 backend/templates/_certificates.conf delete mode 100644 backend/templates/_forced_tls.conf delete mode 100644 backend/templates/_header_comment.conf delete mode 100644 backend/templates/_hsts.conf delete mode 100644 backend/templates/_listen.conf delete mode 100644 backend/templates/_location.conf delete mode 100644 backend/templates/dead_host.conf delete mode 100644 backend/templates/default.conf delete mode 100644 backend/templates/ip_ranges.conf delete mode 100644 backend/templates/proxy_host.conf delete mode 100644 backend/templates/redirection_host.conf delete mode 100644 backend/templates/stream.conf delete mode 100644 darkmode.css delete mode 100644 frontend/.gitignore delete mode 100644 frontend/app-images/default-avatar.jpg delete mode 100644 frontend/app-images/favicons/android-chrome-192x192.png delete mode 100644 frontend/app-images/favicons/android-chrome-512x512.png delete mode 100644 frontend/app-images/favicons/apple-touch-icon.png delete mode 100644 frontend/app-images/favicons/browserconfig.xml delete mode 100644 frontend/app-images/favicons/favicon-16x16.png delete mode 100644 frontend/app-images/favicons/favicon-32x32.png delete mode 100644 frontend/app-images/favicons/favicon.ico delete mode 100644 frontend/app-images/favicons/mstile-150x150.png delete mode 100644 frontend/app-images/favicons/safari-pinned-tab.svg delete mode 100644 frontend/app-images/favicons/site.webmanifest delete mode 100644 frontend/app-images/logo-256.png delete mode 100644 frontend/app-images/logo-text-vertical-grey.png delete mode 120000 frontend/fonts/feather delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff delete mode 100644 frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 delete mode 100644 frontend/html/index.ejs delete mode 100644 frontend/html/login.ejs delete mode 100644 frontend/html/partials/footer.ejs delete mode 100644 frontend/html/partials/header.ejs delete mode 120000 frontend/images delete mode 100644 frontend/js/app/api.js delete mode 100644 frontend/js/app/audit-log/list/item.ejs delete mode 100644 frontend/js/app/audit-log/list/item.js delete mode 100644 frontend/js/app/audit-log/list/main.ejs delete mode 100644 frontend/js/app/audit-log/list/main.js delete mode 100644 frontend/js/app/audit-log/main.ejs delete mode 100644 frontend/js/app/audit-log/main.js delete mode 100644 frontend/js/app/audit-log/meta.ejs delete mode 100644 frontend/js/app/audit-log/meta.js delete mode 100644 frontend/js/app/cache.js delete mode 100644 frontend/js/app/controller.js delete mode 100644 frontend/js/app/dashboard/main.ejs delete mode 100644 frontend/js/app/dashboard/main.js delete mode 100644 frontend/js/app/empty/main.ejs delete mode 100644 frontend/js/app/empty/main.js delete mode 100644 frontend/js/app/error/main.ejs delete mode 100644 frontend/js/app/error/main.js delete mode 100644 frontend/js/app/help/main.ejs delete mode 100644 frontend/js/app/help/main.js delete mode 100644 frontend/js/app/i18n.js delete mode 100644 frontend/js/app/main.js delete mode 100644 frontend/js/app/nginx/access/delete.ejs delete mode 100644 frontend/js/app/nginx/access/delete.js delete mode 100644 frontend/js/app/nginx/access/form.ejs delete mode 100644 frontend/js/app/nginx/access/form.js delete mode 100644 frontend/js/app/nginx/access/form/client.ejs delete mode 100644 frontend/js/app/nginx/access/form/client.js delete mode 100644 frontend/js/app/nginx/access/form/item.ejs delete mode 100644 frontend/js/app/nginx/access/form/item.js delete mode 100644 frontend/js/app/nginx/access/list/item.ejs delete mode 100644 frontend/js/app/nginx/access/list/item.js delete mode 100644 frontend/js/app/nginx/access/list/main.ejs delete mode 100644 frontend/js/app/nginx/access/list/main.js delete mode 100644 frontend/js/app/nginx/access/main.ejs delete mode 100644 frontend/js/app/nginx/access/main.js delete mode 100644 frontend/js/app/nginx/certificates-list-item.ejs delete mode 100644 frontend/js/app/nginx/certificates/delete.ejs delete mode 100644 frontend/js/app/nginx/certificates/delete.js delete mode 100644 frontend/js/app/nginx/certificates/form.ejs delete mode 100644 frontend/js/app/nginx/certificates/form.js delete mode 100644 frontend/js/app/nginx/certificates/list/item.ejs delete mode 100644 frontend/js/app/nginx/certificates/list/item.js delete mode 100644 frontend/js/app/nginx/certificates/list/main.ejs delete mode 100644 frontend/js/app/nginx/certificates/list/main.js delete mode 100644 frontend/js/app/nginx/certificates/main.ejs delete mode 100644 frontend/js/app/nginx/certificates/main.js delete mode 100644 frontend/js/app/nginx/certificates/renew.ejs delete mode 100644 frontend/js/app/nginx/certificates/renew.js delete mode 100644 frontend/js/app/nginx/certificates/test.ejs delete mode 100644 frontend/js/app/nginx/certificates/test.js delete mode 100644 frontend/js/app/nginx/dead/delete.ejs delete mode 100644 frontend/js/app/nginx/dead/delete.js delete mode 100644 frontend/js/app/nginx/dead/form.ejs delete mode 100644 frontend/js/app/nginx/dead/form.js delete mode 100644 frontend/js/app/nginx/dead/list/item.ejs delete mode 100644 frontend/js/app/nginx/dead/list/item.js delete mode 100644 frontend/js/app/nginx/dead/list/main.ejs delete mode 100644 frontend/js/app/nginx/dead/list/main.js delete mode 100644 frontend/js/app/nginx/dead/main.ejs delete mode 100644 frontend/js/app/nginx/dead/main.js delete mode 100644 frontend/js/app/nginx/proxy/access-list-item.ejs delete mode 100644 frontend/js/app/nginx/proxy/delete.ejs delete mode 100644 frontend/js/app/nginx/proxy/delete.js delete mode 100644 frontend/js/app/nginx/proxy/form.ejs delete mode 100644 frontend/js/app/nginx/proxy/form.js delete mode 100644 frontend/js/app/nginx/proxy/list/item.ejs delete mode 100644 frontend/js/app/nginx/proxy/list/item.js delete mode 100644 frontend/js/app/nginx/proxy/list/main.ejs delete mode 100644 frontend/js/app/nginx/proxy/list/main.js delete mode 100644 frontend/js/app/nginx/proxy/location-item.ejs delete mode 100644 frontend/js/app/nginx/proxy/location.js delete mode 100644 frontend/js/app/nginx/proxy/main.ejs delete mode 100644 frontend/js/app/nginx/proxy/main.js delete mode 100644 frontend/js/app/nginx/redirection/delete.ejs delete mode 100644 frontend/js/app/nginx/redirection/delete.js delete mode 100644 frontend/js/app/nginx/redirection/form.ejs delete mode 100644 frontend/js/app/nginx/redirection/form.js delete mode 100644 frontend/js/app/nginx/redirection/list/item.ejs delete mode 100644 frontend/js/app/nginx/redirection/list/item.js delete mode 100644 frontend/js/app/nginx/redirection/list/main.ejs delete mode 100644 frontend/js/app/nginx/redirection/list/main.js delete mode 100644 frontend/js/app/nginx/redirection/main.ejs delete mode 100644 frontend/js/app/nginx/redirection/main.js delete mode 100644 frontend/js/app/nginx/stream/delete.ejs delete mode 100644 frontend/js/app/nginx/stream/delete.js delete mode 100644 frontend/js/app/nginx/stream/form.ejs delete mode 100644 frontend/js/app/nginx/stream/form.js delete mode 100644 frontend/js/app/nginx/stream/list/item.ejs delete mode 100644 frontend/js/app/nginx/stream/list/item.js delete mode 100644 frontend/js/app/nginx/stream/list/main.ejs delete mode 100644 frontend/js/app/nginx/stream/list/main.js delete mode 100644 frontend/js/app/nginx/stream/main.ejs delete mode 100644 frontend/js/app/nginx/stream/main.js delete mode 100644 frontend/js/app/router.js delete mode 100644 frontend/js/app/settings/default-site/main.ejs delete mode 100644 frontend/js/app/settings/default-site/main.js delete mode 100644 frontend/js/app/settings/list/item.ejs delete mode 100644 frontend/js/app/settings/list/item.js delete mode 100644 frontend/js/app/settings/list/main.ejs delete mode 100644 frontend/js/app/settings/list/main.js delete mode 100644 frontend/js/app/settings/main.ejs delete mode 100644 frontend/js/app/settings/main.js delete mode 100644 frontend/js/app/tokens.js delete mode 100644 frontend/js/app/ui/footer/main.ejs delete mode 100644 frontend/js/app/ui/footer/main.js delete mode 100644 frontend/js/app/ui/header/main.ejs delete mode 100644 frontend/js/app/ui/header/main.js delete mode 100644 frontend/js/app/ui/main.ejs delete mode 100644 frontend/js/app/ui/main.js delete mode 100644 frontend/js/app/ui/menu/main.ejs delete mode 100644 frontend/js/app/ui/menu/main.js delete mode 100644 frontend/js/app/user/delete.ejs delete mode 100644 frontend/js/app/user/delete.js delete mode 100644 frontend/js/app/user/form.ejs delete mode 100644 frontend/js/app/user/form.js delete mode 100644 frontend/js/app/user/password.ejs delete mode 100644 frontend/js/app/user/password.js delete mode 100644 frontend/js/app/user/permissions.ejs delete mode 100644 frontend/js/app/user/permissions.js delete mode 100644 frontend/js/app/users/list/item.ejs delete mode 100644 frontend/js/app/users/list/item.js delete mode 100644 frontend/js/app/users/list/main.ejs delete mode 100644 frontend/js/app/users/list/main.js delete mode 100644 frontend/js/app/users/main.ejs delete mode 100644 frontend/js/app/users/main.js delete mode 100644 frontend/js/i18n/messages.json delete mode 100644 frontend/js/index.js delete mode 100644 frontend/js/lib/helpers.js delete mode 100644 frontend/js/lib/marionette.js delete mode 100644 frontend/js/login.js delete mode 100644 frontend/js/login/main.js delete mode 100644 frontend/js/login/ui/login.ejs delete mode 100644 frontend/js/login/ui/login.js delete mode 100644 frontend/js/models/access-list.js delete mode 100644 frontend/js/models/audit-log.js delete mode 100644 frontend/js/models/certificate.js delete mode 100644 frontend/js/models/dead-host.js delete mode 100644 frontend/js/models/proxy-host-location.js delete mode 100644 frontend/js/models/proxy-host.js delete mode 100644 frontend/js/models/redirection-host.js delete mode 100644 frontend/js/models/setting.js delete mode 100644 frontend/js/models/stream.js delete mode 100644 frontend/js/models/user.js delete mode 100644 frontend/package.json delete mode 100644 frontend/scss/custom.scss delete mode 100644 frontend/scss/fonts.scss delete mode 100644 frontend/scss/selectize.scss delete mode 100644 frontend/scss/styles.scss delete mode 100644 frontend/scss/tabler-extra.scss delete mode 100644 frontend/webpack.config.js delete mode 100644 global/README.md delete mode 100644 global/certbot-dns-plugins.json diff --git a/.version b/.version deleted file mode 100644 index 6ceb272ee..000000000 --- a/.version +++ /dev/null @@ -1 +0,0 @@ -2.11.1 diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 149080b91..000000000 --- a/backend/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -config/development.json -data/* -yarn-error.log -tmp -certbot.log -node_modules -core.* - diff --git a/backend/.prettierrc b/backend/.prettierrc deleted file mode 100644 index 6a534db77..000000000 --- a/backend/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "semi": true, - "useTabs": true, - "printWidth": 1000, - "singleQuote": true, - "bracketSameLine": true -} diff --git a/backend/app.js b/backend/app.js deleted file mode 100644 index e2c1d5b56..000000000 --- a/backend/app.js +++ /dev/null @@ -1,87 +0,0 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -const fileUpload = require('express-fileupload'); -const compression = require('compression'); -const config = require('./lib/config'); -const log = require('./logger').express; - -/** - * App - */ -const app = express(); -app.use(fileUpload()); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); - -// Gzip -app.use(compression()); - -/** - * General Logging, BEFORE routes - */ - -app.disable('x-powered-by'); -app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); -app.enable('strict routing'); - -// pretty print JSON when not live -if (config.debug()) { - app.set('json spaces', 2); -} - -// CORS for everything -app.use(require('./lib/express/cors')); - -// General security/cache related headers + server header -app.use(function (req, res, next) { - let x_frame_options = 'DENY'; - - if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) { - x_frame_options = process.env.X_FRAME_OPTIONS; - } - - res.set({ - 'X-XSS-Protection': '1; mode=block', - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': x_frame_options, - 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', - Pragma: 'no-cache', - Expires: 0, - }); - next(); -}); - -app.use(require('./lib/express/jwt')()); -app.use('/', require('./routes/api/main')); - -// production error handler -// no stacktraces leaked to user -// eslint-disable-next-line -app.use(function (err, req, res, next) { - const payload = { - error: { - code: err.status, - message: err.public ? err.message : 'Internal Error', - }, - }; - - if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) { - payload.debug = { - stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null, - previous: err.previous, - }; - } - - // Not every error is worth logging - but this is good for now until it gets annoying. - if (typeof err.stack !== 'undefined' && err.stack) { - if (config.debug()) { - log.debug(err.stack); - } else if (typeof err.public === 'undefined' || !err.public) { - log.warn(err.message); - } - } - - res.status(err.status || 500).send(payload); -}); - -module.exports = app; diff --git a/backend/db.js b/backend/db.js deleted file mode 100644 index c3fec0823..000000000 --- a/backend/db.js +++ /dev/null @@ -1,28 +0,0 @@ -const config = require('./lib/config'); - -if (!config.has('database')) { - throw new Error('Database config does not exist! Please read the instructions: https://github.com/ZoeyVid/NPMplus'); -} - -function generateDbConfig() { - const cfg = config.get('database'); - if (cfg.engine === 'knex-native') { - return cfg.knex; - } - return { - client: cfg.engine, - connection: { - host: cfg.host, - user: cfg.user, - password: cfg.password, - database: cfg.name, - port: cfg.port, - ssl: cfg.tls, - }, - migrations: { - tableName: 'migrations', - }, - }; -} - -module.exports = require('knex')(generateDbConfig()); diff --git a/backend/doc/api.swagger.json b/backend/doc/api.swagger.json deleted file mode 100644 index 4d97d5e06..000000000 --- a/backend/doc/api.swagger.json +++ /dev/null @@ -1,1456 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "NPMplus API", - "version": "2.x.x" - }, - "servers": [ - { - "url": "https://127.0.0.1:81/api" - } - ], - "paths": { - "/": { - "get": { - "operationId": "health", - "summary": "Returns the API health status", - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "status": "OK", - "version": { - "major": 2, - "minor": 1, - "revision": 0 - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/HealthObject" - } - } - } - } - } - } - }, - "/nginx/proxy-hosts": { - "get": { - "operationId": "getProxyHosts", - "summary": "Get all proxy hosts", - "tags": ["Proxy Hosts"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "query", - "name": "expand", - "description": "Expansions", - "schema": { - "type": "string", - "enum": ["access_list", "owner", "certificate"] - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": [ - { - "id": 1, - "created_on": "2023-03-30T01:12:23.000Z", - "modified_on": "2023-03-30T02:15:40.000Z", - "owner_user_id": 1, - "domain_names": ["aasdasdad"], - "forward_host": "asdasd", - "forward_port": 80, - "access_list_id": 0, - "certificate_id": 0, - "ssl_forced": 0, - "caching_enabled": 0, - "block_exploits": 0, - "advanced_config": "sdfsdfsdf", - "meta": { - "letsencrypt_agree": false, - "dns_challenge": false, - "nginx_online": false, - "nginx_err": "Command failed: nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /urs/local/nginx/conf/nginx.conf test failed\n" - }, - "allow_websocket_upgrade": 0, - "http2_support": 0, - "forward_scheme": "http", - "enabled": 1, - "locations": [], - "hsts_enabled": 0, - "hsts_subdomains": 0, - "owner": { - "id": 1, - "created_on": "2023-03-30T01:11:50.000Z", - "modified_on": "2023-03-30T01:11:50.000Z", - "is_deleted": 0, - "is_disabled": 0, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "Admin", - "avatar": "", - "roles": ["admin"] - }, - "access_list": null, - "certificate": null - }, - { - "id": 2, - "created_on": "2023-03-30T02:11:49.000Z", - "modified_on": "2023-03-30T02:11:49.000Z", - "owner_user_id": 1, - "domain_names": ["test.example.com"], - "forward_host": "1.1.1.1", - "forward_port": 80, - "access_list_id": 0, - "certificate_id": 0, - "ssl_forced": 0, - "caching_enabled": 0, - "block_exploits": 0, - "advanced_config": "", - "meta": { - "letsencrypt_agree": false, - "dns_challenge": false, - "nginx_online": true, - "nginx_err": null - }, - "allow_websocket_upgrade": 0, - "http2_support": 0, - "forward_scheme": "http", - "enabled": 1, - "locations": [], - "hsts_enabled": 0, - "hsts_subdomains": 0, - "owner": { - "id": 1, - "created_on": "2023-03-30T01:11:50.000Z", - "modified_on": "2023-03-30T01:11:50.000Z", - "is_deleted": 0, - "is_disabled": 0, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "Admin", - "avatar": "", - "roles": ["admin"] - }, - "access_list": null, - "certificate": null - } - ] - } - }, - "schema": { - "$ref": "#/components/schemas/ProxyHostsList" - } - } - } - } - } - }, - "post": { - "operationId": "createProxyHost", - "summary": "Create a Proxy Host", - "tags": ["Proxy Hosts"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "body", - "name": "proxyhost", - "description": "Proxy Host Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/ProxyHostObject" - } - } - ], - "responses": { - "201": { - "description": "201 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": 3, - "created_on": "2023-03-30T02:31:27.000Z", - "modified_on": "2023-03-30T02:31:27.000Z", - "owner_user_id": 1, - "domain_names": ["test2.example.com"], - "forward_host": "1.1.1.1", - "forward_port": 80, - "access_list_id": 0, - "certificate_id": 0, - "ssl_forced": 0, - "caching_enabled": 0, - "block_exploits": 0, - "advanced_config": "", - "meta": { - "letsencrypt_agree": false, - "dns_challenge": false - }, - "allow_websocket_upgrade": 0, - "http2_support": 0, - "forward_scheme": "http", - "enabled": 1, - "locations": [], - "hsts_enabled": 0, - "hsts_subdomains": 0, - "certificate": null, - "owner": { - "id": 1, - "created_on": "2023-03-30T01:11:50.000Z", - "modified_on": "2023-03-30T01:11:50.000Z", - "is_deleted": 0, - "is_disabled": 0, - "email": "admin@example.com", - "name": "Administrator", - "nickname": "Admin", - "avatar": "", - "roles": ["admin"] - }, - "access_list": null, - "use_default_location": true, - "ipv6": true - } - } - }, - "schema": { - "$ref": "#/components/schemas/ProxyHostObject" - } - } - } - } - } - } - }, - "/schema": { - "get": { - "operationId": "schema", - "responses": { - "200": { - "description": "200 response" - } - }, - "summary": "Returns this swagger API schema" - } - }, - "/tokens": { - "get": { - "operationId": "refreshToken", - "summary": "Refresh your access token", - "tags": ["Tokens"], - "security": [ - { - "BearerAuth": ["tokens"] - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "expires": 1566540510, - "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" - } - } - }, - "schema": { - "$ref": "#/components/schemas/TokenObject" - } - } - } - } - } - }, - "post": { - "operationId": "requestToken", - "parameters": [ - { - "description": "Credentials Payload", - "in": "body", - "name": "credentials", - "required": true, - "schema": { - "additionalProperties": false, - "properties": { - "identity": { - "minLength": 1, - "type": "string" - }, - "scope": { - "minLength": 1, - "type": "string", - "enum": ["user"] - }, - "secret": { - "minLength": 1, - "type": "string" - } - }, - "required": ["identity", "secret"], - "type": "object" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "result": { - "expires": 1566540510, - "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/TokenObject" - } - } - }, - "description": "200 response" - } - }, - "summary": "Request a new access token from credentials", - "tags": ["Tokens"] - } - }, - "/settings": { - "get": { - "operationId": "getSettings", - "summary": "Get all settings", - "tags": ["Settings"], - "security": [ - { - "BearerAuth": ["settings"] - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": [ - { - "id": "default-site", - "name": "Default Site", - "description": "What to show when Nginx is hit with an unknown Host", - "value": "congratulations", - "meta": {} - } - ] - } - }, - "schema": { - "$ref": "#/components/schemas/SettingsList" - } - } - } - } - } - } - }, - "/settings/{settingID}": { - "get": { - "operationId": "getSetting", - "summary": "Get a setting", - "tags": ["Settings"], - "security": [ - { - "BearerAuth": ["settings"] - } - ], - "parameters": [ - { - "in": "path", - "name": "settingID", - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "description": "Setting ID", - "example": "default-site" - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": "default-site", - "name": "Default Site", - "description": "What to show when Nginx is hit with an unknown Host", - "value": "congratulations", - "meta": {} - } - } - }, - "schema": { - "$ref": "#/components/schemas/SettingObject" - } - } - } - } - } - }, - "put": { - "operationId": "updateSetting", - "summary": "Update a setting", - "tags": ["Settings"], - "security": [ - { - "BearerAuth": ["settings"] - } - ], - "parameters": [ - { - "in": "path", - "name": "settingID", - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "description": "Setting ID", - "example": "default-site" - }, - { - "in": "body", - "name": "setting", - "description": "Setting Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/SettingObject" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": "default-site", - "name": "Default Site", - "description": "What to show when Nginx is hit with an unknown Host", - "value": "congratulations", - "meta": {} - } - } - }, - "schema": { - "$ref": "#/components/schemas/SettingObject" - } - } - } - } - } - } - }, - "/users": { - "get": { - "operationId": "getUsers", - "summary": "Get all users", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "query", - "name": "expand", - "description": "Expansions", - "schema": { - "type": "string", - "enum": ["permissions"] - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": [ - { - "id": 1, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"] - } - ] - }, - "withPermissions": { - "value": [ - { - "id": 1, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"], - "permissions": { - "visibility": "all", - "proxy_hosts": "manage", - "redirection_hosts": "manage", - "dead_hosts": "manage", - "streams": "manage", - "access_lists": "manage", - "certificates": "manage" - } - } - ] - } - }, - "schema": { - "$ref": "#/components/schemas/UsersList" - } - } - } - } - } - }, - "post": { - "operationId": "createUser", - "summary": "Create a User", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "body", - "name": "user", - "description": "User Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/UserObject" - } - } - ], - "responses": { - "201": { - "description": "201 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": 2, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"], - "permissions": { - "visibility": "all", - "proxy_hosts": "manage", - "redirection_hosts": "manage", - "dead_hosts": "manage", - "streams": "manage", - "access_lists": "manage", - "certificates": "manage" - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/UserObject" - } - } - } - } - } - } - }, - "/users/{userID}": { - "get": { - "operationId": "getUser", - "summary": "Get a user", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "oneOf": [ - { - "type": "string", - "pattern": "^me$" - }, - { - "type": "integer", - "minimum": 1 - } - ] - }, - "required": true, - "description": "User ID or 'me' for yourself", - "example": 1 - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": 1, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"] - } - } - }, - "schema": { - "$ref": "#/components/schemas/UserObject" - } - } - } - } - } - }, - "put": { - "operationId": "updateUser", - "summary": "Update a User", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "oneOf": [ - { - "type": "string", - "pattern": "^me$" - }, - { - "type": "integer", - "minimum": 1 - } - ] - }, - "required": true, - "description": "User ID or 'me' for yourself", - "example": 2 - }, - { - "in": "body", - "name": "user", - "description": "User Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/UserObject" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "id": 2, - "created_on": "2020-01-30T09:36:08.000Z", - "modified_on": "2020-01-30T09:41:04.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", - "roles": ["admin"] - } - } - }, - "schema": { - "$ref": "#/components/schemas/UserObject" - } - } - } - } - } - }, - "delete": { - "operationId": "deleteUser", - "summary": "Delete a User", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": true, - "description": "User ID", - "example": 2 - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": true - } - }, - "schema": { - "type": "boolean" - } - } - } - } - } - } - }, - "/users/{userID}/auth": { - "put": { - "operationId": "updateUserAuth", - "summary": "Update a User's Authentication", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "oneOf": [ - { - "type": "string", - "pattern": "^me$" - }, - { - "type": "integer", - "minimum": 1 - } - ] - }, - "required": true, - "description": "User ID or 'me' for yourself", - "example": 2 - }, - { - "in": "body", - "name": "user", - "description": "User Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/AuthObject" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": true - } - }, - "schema": { - "type": "boolean" - } - } - } - } - } - } - }, - "/users/{userID}/permissions": { - "put": { - "operationId": "updateUserPermissions", - "summary": "Update a User's Permissions", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": true, - "description": "User ID", - "example": 2 - }, - { - "in": "body", - "name": "user", - "description": "Permissions Payload", - "required": true, - "schema": { - "$ref": "#/components/schemas/PermissionsObject" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": true - } - }, - "schema": { - "type": "boolean" - } - } - } - } - } - } - }, - "/users/{userID}/login": { - "put": { - "operationId": "loginAsUser", - "summary": "Login as this user", - "tags": ["Users"], - "security": [ - { - "BearerAuth": ["users"] - } - ], - "parameters": [ - { - "in": "path", - "name": "userID", - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": true, - "description": "User ID", - "example": 2 - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "token": "eyJhbGciOiJSUzI1NiIsInR...16OjT8B3NLyXg", - "expires": "2020-01-31T10:56:23.239Z", - "user": { - "id": 1, - "created_on": "2020-01-30T10:43:44.000Z", - "modified_on": "2020-01-30T10:43:44.000Z", - "is_disabled": 0, - "email": "jc@jc21.com", - "name": "Jamie Curnow", - "nickname": "James", - "avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm", - "roles": ["admin"] - } - } - } - }, - "schema": { - "type": "object", - "description": "Login object", - "required": ["expires", "token", "user"], - "additionalProperties": false, - "properties": { - "expires": { - "description": "Token Expiry Unix Time", - "example": 1566540249, - "minimum": 1, - "type": "number" - }, - "token": { - "description": "JWT Token", - "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/UserObject" - } - } - } - } - } - } - } - } - }, - "/reports/hosts": { - "get": { - "operationId": "reportsHosts", - "summary": "Report on Host Statistics", - "tags": ["Reports"], - "security": [ - { - "BearerAuth": ["reports"] - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "proxy": 20, - "redirection": 1, - "stream": 0, - "dead": 1 - } - } - }, - "schema": { - "$ref": "#/components/schemas/HostReportObject" - } - } - } - } - } - } - }, - "/audit-log": { - "get": { - "operationId": "getAuditLog", - "summary": "Get Audit Log", - "tags": ["Audit Log"], - "security": [ - { - "BearerAuth": ["audit-log"] - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "examples": { - "default": { - "value": { - "proxy": 20, - "redirection": 1, - "stream": 0, - "dead": 1 - } - } - }, - "schema": { - "$ref": "#/components/schemas/HostReportObject" - } - } - } - } - } - } - } - }, - "components": { - "securitySchemes": { - "BearerAuth": { - "type": "http", - "scheme": "bearer" - } - }, - "schemas": { - "HealthObject": { - "type": "object", - "description": "Health object", - "additionalProperties": false, - "required": ["status", "version"], - "properties": { - "status": { - "type": "string", - "description": "Healthy", - "example": "OK" - }, - "version": { - "type": "object", - "description": "The version object", - "example": { - "major": 2, - "minor": 0, - "revision": 0 - }, - "additionalProperties": false, - "required": ["major", "minor", "revision"], - "properties": { - "major": { - "type": "integer", - "minimum": 0 - }, - "minor": { - "type": "integer", - "minimum": 0 - }, - "revision": { - "type": "integer", - "minimum": 0 - } - } - } - } - }, - "TokenObject": { - "type": "object", - "description": "Token object", - "required": ["expires", "token"], - "additionalProperties": false, - "properties": { - "expires": { - "description": "Token Expiry Unix Time", - "example": 1566540249, - "minimum": 1, - "type": "number" - }, - "token": { - "description": "JWT Token", - "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", - "type": "string" - } - } - }, - "ProxyHostObject": { - "type": "object", - "description": "Proxy Host object", - "required": [ - "id", - "created_on", - "modified_on", - "owner_user_id", - "domain_names", - "forward_host", - "forward_port", - "access_list_id", - "certificate_id", - "ssl_forced", - "caching_enabled", - "block_exploits", - "advanced_config", - "meta", - "allow_websocket_upgrade", - "http2_support", - "forward_scheme", - "enabled", - "locations", - "hsts_enabled", - "hsts_subdomains", - "certificate", - "use_default_location", - "ipv6" - ], - "additionalProperties": false, - "properties": { - "id": { - "type": "integer", - "description": "Proxy Host ID", - "minimum": 1, - "example": 1 - }, - "created_on": { - "type": "string", - "description": "Created Date", - "example": "2020-01-30T09:36:08.000Z" - }, - "modified_on": { - "type": "string", - "description": "Modified Date", - "example": "2020-01-30T09:41:04.000Z" - }, - "owner_user_id": { - "type": "integer", - "minimum": 1, - "example": 1 - }, - "domain_names": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "forward_host": { - "type": "string", - "minLength": 1 - }, - "forward_port": { - "type": "integer", - "minimum": 1 - }, - "access_list_id": { - "type": "integer" - }, - "certificate_id": { - "type": "integer" - }, - "ssl_forced": { - "type": "integer" - }, - "caching_enabled": { - "type": "integer" - }, - "block_exploits": { - "type": "integer" - }, - "advanced_config": { - "type": "string" - }, - "meta": { - "type": "object" - }, - "allow_websocket_upgrade": { - "type": "integer" - }, - "http2_support": { - "type": "integer" - }, - "forward_scheme": { - "type": "string" - }, - "enabled": { - "type": "integer" - }, - "locations": { - "type": "array" - }, - "hsts_enabled": { - "type": "integer" - }, - "hsts_subdomains": { - "type": "integer" - }, - "certificate": { - "type": "object", - "nullable": true - }, - "owner": { - "type": "object", - "nullable": true - }, - "access_list": { - "type": "object", - "nullable": true - }, - "use_default_location": { - "type": "boolean" - }, - "ipv6": { - "type": "boolean" - } - } - }, - "ProxyHostsList": { - "type": "array", - "description": "Proxyn Hosts list", - "items": { - "$ref": "#/components/schemas/ProxyHostObject" - } - }, - "SettingObject": { - "type": "object", - "description": "Setting object", - "required": ["id", "name", "description", "value", "meta"], - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "description": "Setting ID", - "minLength": 1, - "example": "default-site" - }, - "name": { - "type": "string", - "description": "Setting Display Name", - "minLength": 1, - "example": "Default Site" - }, - "description": { - "type": "string", - "description": "Meaningful description", - "minLength": 1, - "example": "What to show when Nginx is hit with an unknown Host" - }, - "value": { - "description": "Value in almost any form", - "example": "congratulations", - "oneOf": [ - { - "type": "string", - "minLength": 1 - }, - { - "type": "integer" - }, - { - "type": "object" - }, - { - "type": "number" - }, - { - "type": "array" - } - ] - }, - "meta": { - "description": "Extra metadata", - "example": {}, - "type": "object" - } - } - }, - "SettingsList": { - "type": "array", - "description": "Setting list", - "items": { - "$ref": "#/components/schemas/SettingObject" - } - }, - "UserObject": { - "type": "object", - "description": "User object", - "required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"], - "additionalProperties": false, - "properties": { - "id": { - "type": "integer", - "description": "User ID", - "minimum": 1, - "example": 1 - }, - "created_on": { - "type": "string", - "description": "Created Date", - "example": "2020-01-30T09:36:08.000Z" - }, - "modified_on": { - "type": "string", - "description": "Modified Date", - "example": "2020-01-30T09:41:04.000Z" - }, - "is_disabled": { - "type": "integer", - "minimum": 0, - "maximum": 1, - "description": "Is user Disabled (0 = false, 1 = true)", - "example": 0 - }, - "email": { - "type": "string", - "description": "Email", - "minLength": 3, - "example": "jc@jc21.com" - }, - "name": { - "type": "string", - "description": "Name", - "minLength": 1, - "example": "Jamie Curnow" - }, - "nickname": { - "type": "string", - "description": "Nickname", - "example": "James" - }, - "avatar": { - "type": "string", - "description": "Gravatar URL based on email, without scheme", - "example": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm" - }, - "roles": { - "description": "Roles applied", - "example": ["admin"], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "UsersList": { - "type": "array", - "description": "User list", - "items": { - "$ref": "#/components/schemas/UserObject" - } - }, - "AuthObject": { - "type": "object", - "description": "Authentication Object", - "required": ["type", "secret"], - "properties": { - "type": { - "type": "string", - "pattern": "^password$", - "example": "password" - }, - "current": { - "type": "string", - "minLength": 1, - "maxLength": 99, - "example": "iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi" - }, - "secret": { - "type": "string", - "minLength": 8, - "maxLength": 99, - "example": "5wdvvveVKkNNr8K7fSQKoUWbYyCZ2abtLaa1J5LzAvMfkGVcGBXHQ32iuPdeKdNfQVZiPKee3ZPKaGMvFR5t94QCeZbK3faSVYu" - } - } - }, - "PermissionsObject": { - "type": "object", - "properties": { - "visibility": { - "type": "string", - "description": "Visibility Type", - "enum": ["all", "user"] - }, - "access_lists": { - "type": "string", - "description": "Access Lists Permissions", - "enum": ["hidden", "view", "manage"] - }, - "dead_hosts": { - "type": "string", - "description": "404 Hosts Permissions", - "enum": ["hidden", "view", "manage"] - }, - "proxy_hosts": { - "type": "string", - "description": "Proxy Hosts Permissions", - "enum": ["hidden", "view", "manage"] - }, - "redirection_hosts": { - "type": "string", - "description": "Redirection Permissions", - "enum": ["hidden", "view", "manage"] - }, - "streams": { - "type": "string", - "description": "Streams Permissions", - "enum": ["hidden", "view", "manage"] - }, - "certificates": { - "type": "string", - "description": "Certificates Permissions", - "enum": ["hidden", "view", "manage"] - } - } - }, - "HostReportObject": { - "type": "object", - "properties": { - "proxy": { - "type": "integer", - "description": "Proxy Hosts Count" - }, - "redirection": { - "type": "integer", - "description": "Redirection Hosts Count" - }, - "stream": { - "type": "integer", - "description": "Streams Count" - }, - "dead": { - "type": "integer", - "description": "404 Hosts Count" - } - } - } - } - } -} diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs deleted file mode 100644 index a4394115d..000000000 --- a/backend/eslint.config.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import globals from 'globals'; -import pluginJs from '@eslint/js'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; - -export default [{ files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, eslintPluginPrettierRecommended]; diff --git a/backend/index.js b/backend/index.js deleted file mode 100755 index 13244e52a..000000000 --- a/backend/index.js +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node - -const logger = require('./logger').global; - -async function appStart() { - const migrate = require('./migrate'); - const setup = require('./setup'); - const app = require('./app'); - const apiValidator = require('./lib/validator/api'); - const internalNginx = require('./internal/nginx'); - const internalCertificate = require('./internal/certificate'); - const internalIpRanges = require('./internal/ip_ranges'); - - return migrate - .latest() - .then(setup) - .then(() => { - return apiValidator.loadSchemas; - }) - .then(internalIpRanges.fetch) - .then(() => { - internalNginx.reload(); - internalCertificate.initTimer(); - internalIpRanges.initTimer(); - - const server = app.listen(48693, '127.0.0.1', () => { - logger.info('Backend PID ' + process.pid + ' listening on port 48693 ...'); - - process.on('SIGTERM', () => { - logger.info('PID ' + process.pid + ' received SIGTERM'); - server.close(() => { - logger.info('Stopping.'); - process.exit(0); - }); - }); - }); - }) - .catch((err) => { - logger.error(err.message); - setTimeout(appStart, 1000); - }); -} - -try { - appStart(); -} catch (err) { - logger.error(err.message, err); - process.exit(1); -} diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js deleted file mode 100644 index f2fe9fdb4..000000000 --- a/backend/internal/access-list.js +++ /dev/null @@ -1,511 +0,0 @@ -const _ = require('lodash'); -const fs = require('fs'); -const batchflow = require('batchflow'); -const logger = require('../logger').access; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const accessListModel = require('../models/access_list'); -const accessListAuthModel = require('../models/access_list_auth'); -const accessListClientModel = require('../models/access_list_client'); -const proxyHostModel = require('../models/proxy_host'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); - -function omissions() { - return ['is_deleted']; -} - -const internalAccessList = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - return access - .can('access_lists:create', data) - .then((/* access_data */) => { - return accessListModel - .query() - .insertAndFetch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - owner_user_id: access.token.getUserId(1), - }) - .then(utils.omitRow(omissions())); - }) - .then((row) => { - data.id = row.id; - - const promises = []; - - // Now add the items - data.items.map((item) => { - promises.push( - accessListAuthModel.query().insert({ - access_list_id: row.id, - username: item.username, - password: item.password, - }), - ); - }); - - // Now add the clients - if (typeof data.clients !== 'undefined' && data.clients) { - data.clients.map((client) => { - promises.push( - accessListClientModel.query().insert({ - access_list_id: row.id, - address: client.address, - directive: client.directive, - }), - ); - }); - } - - return Promise.all(promises); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get( - access, - { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'], - }, - true /* <- skip masking */, - ); - }) - .then((row) => { - // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); - - return internalAccessList - .build(row) - .then(() => { - if (row.proxy_host_count) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'created', - object_type: 'access-list', - object_id: row.id, - meta: internalAccessList.maskItems(data), - }); - }) - .then(() => { - return internalAccessList.maskItems(row); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {String} [data.name] - * @param {String} [data.items] - * @return {Promise} - */ - update: (access, data) => { - return access - .can('access_lists:update', data.id) - .then((/* access_data */) => { - return internalAccessList.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - }) - .then(() => { - // patch name if specified - if (typeof data.name !== 'undefined' && data.name) { - return accessListModel.query().where({ id: data.id }).patch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - }); - } - }) - .then(() => { - // Check for items and add/update/remove them - if (typeof data.items !== 'undefined' && data.items) { - const promises = []; - const items_to_keep = []; - - data.items.map(function (item) { - if (item.password) { - promises.push( - accessListAuthModel.query().insert({ - access_list_id: data.id, - username: item.username, - password: item.password, - }), - ); - } else { - // This was supplied with an empty password, which means keep it but don't change the password - items_to_keep.push(item.username); - } - }); - - const query = accessListAuthModel.query().delete().where('access_list_id', data.id); - - if (items_to_keep.length) { - query.andWhere('username', 'NOT IN', items_to_keep); - } - - return query.then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Check for clients and add/update/remove them - if (typeof data.clients !== 'undefined' && data.clients) { - const promises = []; - - data.clients.map(function (client) { - if (client.address) { - promises.push( - accessListClientModel.query().insert({ - access_list_id: data.id, - address: client.address, - directive: client.directive, - }), - ); - } - }); - - const query = accessListClientModel.query().delete().where('access_list_id', data.id); - - return query.then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'access-list', - object_id: data.id, - meta: internalAccessList.maskItems(data), - }); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get( - access, - { - id: data.id, - expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'], - }, - true /* <- skip masking */, - ); - }) - .then((row) => { - return internalAccessList - .build(row) - .then(() => { - if (row.proxy_host_count) { - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - } - }) - .then(internalNginx.reload) - .then(() => { - return internalAccessList.maskItems(row); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @param {Boolean} [skip_masking] - * @return {Promise} - */ - get: (access, data, skip_masking) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('access_lists:get', data.id) - .then((access_data) => { - const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).andWhere('access_list.id', data.id).allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - if (!skip_masking && typeof row.items !== 'undefined' && row.items) { - row = internalAccessList.maskItems(row); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('access_lists:delete', data.id) - .then(() => { - return internalAccessList.get(access, { id: data.id, expand: ['proxy_hosts', 'items', 'clients'] }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - // 1. update row to be deleted - // 2. update any proxy hosts that were using it (ignoring permissions) - // 3. reconfigure those hosts - // 4. audit log - - // 1. update row to be deleted - return accessListModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // 2. update any proxy hosts that were using it (ignoring permissions) - if (row.proxy_hosts) { - return proxyHostModel - .query() - .where('access_list_id', '=', row.id) - .patch({ access_list_id: 0 }) - .then(() => { - // 3. reconfigure those hosts, then reload nginx - - // set the access_list_id to zero for these items - row.proxy_hosts.map(function (val, idx) { - row.proxy_hosts[idx].access_list_id = 0; - }); - - return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); - }) - .then(() => { - return internalNginx.reload(); - }); - } - }) - .then(() => { - // delete the htpasswd file - const htpasswd_file = internalAccessList.getFilename(row); - - try { - fs.unlinkSync(htpasswd_file); - } catch { - // do nothing - } - }) - .then(() => { - // 4. audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'access-list', - object_id: row.id, - meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts']), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Lists - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access - .can('access_lists:list') - .then((access_data) => { - const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).groupBy('access_list.id').allowGraph('[owner,items,clients]').orderBy('access_list.name', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('access_list.owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (rows) { - rows.map(function (row, idx) { - if (typeof row.items !== 'undefined' && row.items) { - rows[idx] = internalAccessList.maskItems(row); - } - }); - } - - return rows; - }); - }, - - /** - * Report use - * - * @param {Integer} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = accessListModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, - - /** - * @param {Object} list - * @returns {Object} - */ - maskItems: (list) => { - if (list && typeof list.items !== 'undefined') { - list.items.map(function (val, idx) { - let repeat_for = 8; - let first_char = '*'; - - if (typeof val.password !== 'undefined' && val.password) { - repeat_for = val.password.length - 1; - first_char = val.password.charAt(0); - } - - list.items[idx].hint = first_char + '*'.repeat(repeat_for); - list.items[idx].password = ''; - }); - } - - return list; - }, - - /** - * @param {Object} list - * @param {Integer} list.id - * @returns {String} - */ - getFilename: (list) => { - return '/data/etc/access/' + list.id; - }, - - /** - * @param {Object} list - * @param {Integer} list.id - * @param {String} list.name - * @param {Array} list.items - * @returns {Promise} - */ - build: (list) => { - logger.info('Building Access file #' + list.id + ' for: ' + list.name); - - return new Promise((resolve, reject) => { - const htpasswd_file = internalAccessList.getFilename(list); - - // 1. remove any existing access file - try { - fs.unlinkSync(htpasswd_file); - } catch { - // do nothing - } - - // 2. create empty access file - try { - fs.writeFileSync(htpasswd_file, '', { encoding: 'utf8' }); - resolve(htpasswd_file); - } catch (err) { - reject(err); - } - }).then((htpasswd_file) => { - // 3. generate password for each user - if (list.items.length) { - return new Promise((resolve, reject) => { - batchflow(list.items) - .sequential() - .each((i, item, next) => { - if (typeof item.password !== 'undefined' && item.password.length) { - logger.info('Adding: ' + item.username); - - utils - .execFile('htpasswd', ['-b', htpasswd_file, item.username, item.password]) - .then((/* result */) => { - next(); - }) - .catch((err) => { - logger.error(err); - next(err); - }); - } - }) - .error((err) => { - logger.error(err); - reject(err); - }) - .end((results) => { - logger.success('Built Access file #' + list.id + ' for: ' + list.name); - resolve(results); - }); - }); - } - }); - }, -}; - -module.exports = internalAccessList; diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js deleted file mode 100644 index cd6c61074..000000000 --- a/backend/internal/audit-log.js +++ /dev/null @@ -1,71 +0,0 @@ -const error = require('../lib/error'); -const auditLogModel = require('../models/audit-log'); - -const internalAuditLog = { - /** - * All logs - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access.can('auditlog:list').then(() => { - const query = auditLogModel.query().orderBy('created_on', 'DESC').orderBy('id', 'DESC').limit(100).allowGraph('[user]'); - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('meta', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query; - }); - }, - - /** - * This method should not be publicly used, it doesn't check certain things. It will be assumed - * that permission to add to audit log is already considered, however the access token is used for - * default user id determination. - * - * @param {Access} access - * @param {Object} data - * @param {String} data.action - * @param {Number} [data.user_id] - * @param {Number} [data.object_id] - * @param {Number} [data.object_type] - * @param {Object} [data.meta] - * @returns {Promise} - */ - add: (access, data) => { - return new Promise((resolve, reject) => { - // Default the user id - if (typeof data.user_id === 'undefined' || !data.user_id) { - data.user_id = access.token.getUserId(1); - } - - if (typeof data.action === 'undefined' || !data.action) { - reject(new error.InternalValidationError('Audit log entry must contain an Action')); - } else { - // Make sure at least 1 of the IDs are set and action - resolve( - auditLogModel.query().insert({ - user_id: data.user_id, - action: data.action, - object_type: data.object_type || '', - object_id: data.object_id || 0, - meta: data.meta || {}, - }), - ); - } - }); - }, -}; - -module.exports = internalAuditLog; diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js deleted file mode 100644 index 97829cd4f..000000000 --- a/backend/internal/certificate.js +++ /dev/null @@ -1,1125 +0,0 @@ -const _ = require('lodash'); -const fs = require('fs'); -const https = require('https'); -const moment = require('moment'); -const logger = require('../logger').ssl; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const certificateModel = require('../models/certificate'); -const dnsPlugins = require('../certbot-dns-plugins.json'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); -const certbot = require('../lib/certbot'); -const archiver = require('archiver'); -const crypto = require('crypto'); -const path = require('path'); -const { isArray } = require('lodash'); - -const certbotConfig = '/data/tls/certbot/config.ini'; -const certbotCommand = 'certbot --logs-dir /tmp/certbot-log --work-dir /tmp/certbot-work --config-dir /data/tls/certbot'; - -function omissions() { - return ['is_deleted']; -} - -const internalCertificate = { - allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'], - intervalTimeout: 1000 * 60 * 60 * Number(process.env.CRT), - interval: null, - intervalProcessing: false, - - initTimer: () => { - logger.info('Certbot Renewal Timer initialized'); - internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout); - }, - - /** - * Triggered by a timer, this will check for expiring hosts and renew their tls certs if required - */ - processExpiringHosts: () => { - if (!internalCertificate.intervalProcessing) { - internalCertificate.intervalProcessing = true; - logger.info('Renewing TLS certs close to expiry...'); - - const cmd = certbotCommand + ' renew --quiet ' + '--config "' + certbotConfig + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew'; - - return utils - .exec(cmd) - .then((result) => { - if (result) { - logger.info('Renew Result: ' + result); - } - - return internalNginx.reload().then(() => { - logger.info('Renew Complete'); - return result; - }); - }) - .then(() => { - // Now go and fetch all the certbot certs from the db and query the files and update expiry times - return certificateModel - .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') - .then((certificates) => { - if (certificates && certificates.length) { - const promises = []; - - certificates.map(function (certificate) { - promises.push( - internalCertificate - .getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem') - .then((cert_info) => { - return certificateModel - .query() - .where('id', certificate.id) - .andWhere('provider', 'letsencrypt') - .patch({ - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - }); - }) - .catch((err) => { - // Don't want to stop the train here, just log the error - logger.error(err.message); - }), - ); - }); - - return Promise.all(promises); - } - }); - }) - .then(() => { - internalCertificate.intervalProcessing = false; - }) - .catch((err) => { - logger.error(err); - internalCertificate.intervalProcessing = false; - }); - } - }, - - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - return access - .can('certificates:create', data) - .then(() => { - data.owner_user_id = access.token.getUserId(1); - - if (data.provider === 'letsencrypt') { - data.nice_name = data.domain_names.join(', '); - } - - return certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - // Request a new Cert using Certbot. Let the fun begin. - if (certificate.meta.dns_challenge) { - return internalCertificate - .requestLetsEncryptSslWithDnsChallenge(certificate) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, throw err back - throw err; - }); - } else { - return internalCertificate - .requestLetsEncryptSsl(certificate) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, throw err back - throw err; - }); - } - } else { - return certificate; - } - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - // At this point, the certbot cert should exist on disk. - // Lets get the expiry date from the file and update the row silently - return internalCertificate - .getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem') - .then((cert_info) => { - return certificateModel - .query() - .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - }) - .then((saved_row) => { - // Add cert data for audit log - saved_row.meta = _.assign({}, saved_row.meta, { - letsencrypt_certificate: cert_info, - }); - - return saved_row; - }); - }) - .catch(async (error) => { - // Delete the certificate from the database if it was not created successfully - await certificateModel.query().deleteById(certificate.id); - - throw error; - }); - } else { - return certificate; - } - }) - .then((certificate) => { - data.meta = _.assign({}, data.meta || {}, certificate.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'certificate', - object_id: certificate.id, - meta: data, - }) - .then(() => { - return certificate; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.email] - * @param {String} [data.name] - * @return {Promise} - */ - update: (access, data) => { - return access - .can('certificates:update', data.id) - .then((/* access_data */) => { - return internalCertificate.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - return certificateModel - .query() - .patchAndFetchById(row.id, data) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - saved_row.meta = internalCertificate.cleanMeta(saved_row.meta); - data.meta = internalCertificate.cleanMeta(data.meta); - - // Add row.nice_name for custom certs - if (saved_row.provider === 'other') { - data.nice_name = saved_row.nice_name; - } - - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(data, ['expires_on']), // this prevents json circular reference because expires_on might be raw - }) - .then(() => { - return saved_row; - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('certificates:get', data.id) - .then((access_data) => { - const query = certificateModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - download: (access, data) => { - return new Promise((resolve, reject) => { - access - .can('certificates:get', data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - const zipDirectory = '/data/tls/certbot/live/npm-' + data.id; - - if (!fs.existsSync(zipDirectory)) { - throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists'); - } - - const certFiles = fs - .readdirSync(zipDirectory) - .filter((fn) => fn.endsWith('.pem')) - .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; - const opName = '/tmp/' + downloadName; - internalCertificate - .zipFiles(certFiles, opName) - .then(() => { - logger.debug('zip completed : ', opName); - const resp = { - fileName: opName, - }; - resolve(resp); - }) - .catch((err) => reject(err)); - } else { - throw new error.ValidationError('Only Certbot certificates can be downloaded'); - } - }) - .catch((err) => reject(err)); - }); - }, - - /** - * @param {String} source - * @param {String} out - * @returns {Promise} - */ - zipFiles(source, out) { - const archive = archiver('zip', { zlib: { level: 9 } }); - const stream = fs.createWriteStream(out); - - return new Promise((resolve, reject) => { - source.map((fl) => { - const fileName = path.basename(fl); - logger.debug(fl, 'added to certificate zip'); - archive.file(fl, { name: fileName }); - }); - archive.on('error', (err) => reject(err)).pipe(stream); - - stream.on('close', () => resolve()); - archive.finalize(); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('certificates:delete', data.id) - .then(() => { - return internalCertificate.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - return certificateModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Add to audit log - row.meta = internalCertificate.cleanMeta(row.meta); - - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'certificate', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }) - .then(() => { - if (row.provider === 'letsencrypt') { - // Revoke the cert - return internalCertificate.revokeLetsEncryptSsl(row); - } - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Certs - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access.can('certificates:list').then((access_data) => { - const query = certificateModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('nice_name', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('nice_name', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }); - }, - - /** - * Report use - * - * @param {Number} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = certificateModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, - - /** - * @param {Object} certificate - * @returns {Promise} - */ - writeCustomCert: (certificate) => { - logger.info('Writing Custom Certificate:', certificate); - - const dir = '/data/tls/custom/npm-' + certificate.id; - - return new Promise((resolve, reject) => { - if (certificate.provider === 'letsencrypt') { - reject(new Error('Refusing to write certbot certs here')); - return; - } - - let certData = certificate.meta.certificate; - if (typeof certificate.meta.intermediate_certificate !== 'undefined') { - certData = certData + '\n' + certificate.meta.intermediate_certificate; - } - - try { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - } catch (err) { - reject(err); - return; - } - - fs.writeFile(dir + '/fullchain.pem', certData, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - - fs.writeFile(dir + '/chain.pem', certificate.meta.intermediate_certificate, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }).then(() => { - return new Promise((resolve, reject) => { - fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Array} data.domain_names - * @param {String} data.meta.letsencrypt_email - * @param {Boolean} data.meta.letsencrypt_agree - * @returns {Promise} - */ - createQuickCertificate: (access, data) => { - return internalCertificate.create(access, { - provider: 'letsencrypt', - domain_names: data.domain_names, - meta: data.meta, - }); - }, - - /** - * Validates that the certs provided are good. - * No access required here, nothing is changed or stored. - * - * @param {Object} data - * @param {Object} data.files - * @returns {Promise} - */ - validate: (data) => { - return new Promise((resolve) => { - // Put file contents into an object - const files = {}; - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - files[name] = file.data.toString(); - } - }); - - resolve(files); - }).then((files) => { - // For each file, create a temp file and write the contents to it - // Then test it depending on the file type - const promises = []; - _.map(files, (content, type) => { - promises.push( - new Promise((resolve) => { - if (type === 'certificate_key') { - resolve(internalCertificate.checkPrivateKey(content)); - } else { - // this should handle `certificate` and intermediate certificate - resolve(internalCertificate.getCertificateInfo(content, true)); - } - }).then((res) => { - return { [type]: res }; - }), - ); - }); - - return Promise.all(promises).then((files) => { - let data = {}; - - _.each(files, (file) => { - data = _.assign({}, data, file); - }); - - return data; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Object} data.files - * @returns {Promise} - */ - upload: (access, data) => { - return internalCertificate.get(access, { id: data.id }).then((row) => { - if (row.provider !== 'other') { - throw new error.ValidationError('Cannot upload certificates for this type of provider'); - } - - return internalCertificate - .validate(data) - .then((validations) => { - if (typeof validations.certificate === 'undefined') { - throw new error.ValidationError('Certificate file was not provided'); - } - - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - row.meta[name] = file.data.toString(); - } - }); - - // TODO: This uses a mysql only raw function that won't translate to postgres - return internalCertificate - .update(access, { - id: data.id, - expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - domain_names: [validations.certificate.cn], - meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later - }) - .then((certificate) => { - certificate.meta = row.meta; - return internalCertificate.writeCustomCert(certificate); - }); - }) - .then(() => { - return _.pick(row.meta, internalCertificate.allowedSslFiles); - }); - }); - }, - - /** - * Uses the openssl command to validate the private key. - * It will save the file to disk first, then run commands on it, then delete the file. - * - * @param {String} private_key This is the entire key contents as a string - */ - checkPrivateKey: (private_key) => { - const randomName = crypto.randomBytes(8).toString('hex'); - const filepath = path.join('/tmp', 'certificate_' + randomName); - fs.writeFileSync(filepath, private_key); - return new Promise((resolve, reject) => { - const failTimeout = setTimeout(() => { - reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); - }, 10000); - utils - .exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ') - .then((result) => { - clearTimeout(failTimeout); - if (!result.toLowerCase().includes('key is valid')) { - reject(new error.ValidationError('Result Validation Error: ' + result)); - } - fs.unlinkSync(filepath); - resolve(true); - }) - .catch((err) => { - clearTimeout(failTimeout); - fs.unlinkSync(filepath); - reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err)); - }); - }); - }, - - /** - * Uses the openssl command to both validate and get info out of the certificate. - * It will save the file to disk first, then run commands on it, then delete the file. - * - * @param {String} certificate This is the entire cert contents as a string - * @param {Boolean} [throw_expired] Throw when the certificate is out of date - */ - getCertificateInfo: (certificate, throw_expired) => { - const randomName = crypto.randomBytes(8).toString('hex'); - const filepath = path.join('/tmp', 'certificate_' + randomName); - fs.writeFileSync(filepath, certificate); - return internalCertificate - .getCertificateInfoFromFile(filepath, throw_expired) - .then((certData) => { - fs.unlinkSync(filepath); - return certData; - }) - .catch((err) => { - fs.unlinkSync(filepath); - throw err; - }); - }, - - /** - * Uses the openssl command to both validate and get info out of the certificate. - * It will save the file to disk first, then run commands on it, then delete the file. - * - * @param {String} certificate_file The file location on disk - * @param {Boolean} [throw_expired] Throw when the certificate is out of date - */ - getCertificateInfoFromFile: (certificate_file, throw_expired) => { - const certData = {}; - - return utils - .exec('openssl x509 -in ' + certificate_file + ' -subject -noout') - .then((result) => { - // subject=CN = something.example.com - const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; - const match = regex.exec(result); - - if (typeof match[1] === 'undefined') { - throw new error.ValidationError('Could not determine subject from certificate: ' + result); - } - - certData.cn = match[1]; - }) - .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); - }) - .then((result) => { - const regex = /^(?:issuer=)?(.*)$/gim; - const match = regex.exec(result); - - if (typeof match[1] === 'undefined') { - throw new error.ValidationError('Could not determine issuer from certificate: ' + result); - } - - certData.issuer = match[1]; - }) - .then(() => { - return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); - }) - .then((result) => { - // notBefore=Jul 14 04:04:29 2018 GMT - // notAfter=Oct 12 04:04:29 2018 GMT - let validFrom = null; - let validTo = null; - - const lines = result.split('\n'); - lines.map(function (str) { - const regex = /^(\S+)=(.*)$/gim; - const match = regex.exec(str.trim()); - - if (match && typeof match[2] !== 'undefined') { - const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10); - - if (match[1].toLowerCase() === 'notbefore') { - validFrom = date; - } else if (match[1].toLowerCase() === 'notafter') { - validTo = date; - } - } - }); - - if (!validFrom || !validTo) { - throw new error.ValidationError('Could not determine dates from certificate: ' + result); - } - - if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { - throw new error.ValidationError('Certificate has expired'); - } - - certData.dates = { - from: validFrom, - to: validTo, - }; - - return certData; - }) - .catch((err) => { - throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); - }); - }, - - /** - * Cleans the tls keys from the meta object and sets them to "true" - * - * @param {Object} meta - * @param {Boolean} [remove] - * @returns {Object} - */ - cleanMeta: function (meta, remove) { - internalCertificate.allowedSslFiles.map((key) => { - if (typeof meta[key] !== 'undefined' && meta[key]) { - if (remove) { - delete meta[key]; - } else { - meta[key] = true; - } - } - }); - - return meta; - }, - - /** - * Request a certificate using the http challenge - * @param {Object} certificate the certificate row - * @returns {Promise} - */ - requestLetsEncryptSsl: (certificate) => { - logger.info('Requesting Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); - - let cmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--authenticator webroot ' + '--preferred-challenges "dns,http" ' + '--domains "' + certificate.domain_names.join(',') + '"'; - - if (certificate.meta.letsencrypt_email === '') { - cmd = cmd + ' --register-unsafely-without-email '; - } else { - cmd = cmd + ' --email "' + certificate.meta.letsencrypt_email + '" '; - } - - logger.info('Command:', cmd); - - return utils.exec(cmd).then((result) => { - logger.success(result); - return result; - }); - }, - - /** - * @param {Object} certificate the certificate row - * @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`) - * @param {String | null} credentials the content of this providers credentials file - * @param {String} propagation_seconds the time to wait until the dns record should be changed - * @returns {Promise} - */ - requestLetsEncryptSslWithDnsChallenge: async (certificate) => { - await certbot.installPlugin(certificate.meta.dns_provider); - const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - logger.info(`Requesting Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - - const credentialsLocation = '/data/tls/certbot/credentials/credentials-' + certificate.id; - // Escape single quotes and backslashes - const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll("'", "\\'").replaceAll('\\', '\\\\'); - const credentialsCmd = `echo '${escapedCredentials}' | tee '${credentialsLocation}'`; - - let mainCmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--domains "' + certificate.domain_names.join(',') + '" ' + '--authenticator ' + dnsPlugin.full_plugin_name + ' ' + '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"' + (certificate.meta.propagation_seconds !== undefined ? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds : ''); - - if (certificate.meta.letsencrypt_email === '') { - mainCmd = mainCmd + ' --register-unsafely-without-email '; - } else { - mainCmd = mainCmd + ' --email "' + certificate.meta.letsencrypt_email + '" '; - } - - logger.info('Command:', `${credentialsCmd} && ${mainCmd}`); - - try { - await utils.exec(credentialsCmd); - const result = await utils.exec(mainCmd); - logger.info(result); - return result; - } catch (err) { - // Don't fail if file does not exist - const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`; - await utils.exec(delete_credentialsCmd); - throw err; - } - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - renew: (access, data) => { - return access - .can('certificates:update', data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === 'letsencrypt') { - const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl; - - return renewMethod(certificate) - .then(() => { - return internalCertificate.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem'); - }) - .then((cert_info) => { - return certificateModel.query().patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'), - }); - }) - .then((updated_certificate) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'renewed', - object_type: 'certificate', - object_id: updated_certificate.id, - meta: updated_certificate, - }) - .then(() => { - return updated_certificate; - }); - }); - } else { - throw new error.ValidationError('Only Certbot certificates can be renewed'); - } - }); - }, - - /** - * @param {Object} certificate the certificate row - * @returns {Promise} - */ - renewLetsEncryptSsl: (certificate) => { - logger.info('Renewing Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); - - const cmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew'; - - logger.info('Command:', cmd); - - return utils.exec(cmd).then((result) => { - logger.info(result); - return result; - }); - }, - - /** - * @param {Object} certificate the certificate row - * @returns {Promise} - */ - renewLetsEncryptSslWithDnsChallenge: (certificate) => { - const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - - if (!dnsPlugin) { - throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); - } - - logger.info(`Renewing Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); - - const mainCmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew'; - - logger.info('Command:', mainCmd); - - return utils.exec(mainCmd).then(async (result) => { - logger.info(result); - return result; - }); - }, - - /** - * @param {Object} certificate the certificate row - * @param {Boolean} [throw_errors] - * @returns {Promise} - */ - revokeLetsEncryptSsl: (certificate, throw_errors) => { - logger.info('Revoking Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); - - const mainCmd = certbotCommand + ' revoke ' + '--config "' + certbotConfig + '" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" ' + '--delete-after-revoke'; - - // Don't fail command if file does not exist - const delete_credentialsCmd = `rm -f '/data/tls/certbot/credentials/credentials-${certificate.id}' || true`; - - logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); - - return utils - .exec(mainCmd) - .then(async (result) => { - await utils.exec(delete_credentialsCmd); - logger.info(result); - return result; - }) - .catch((err) => { - logger.error(err.message); - - if (throw_errors) { - throw err; - } - }); - }, - - /** - * @param {Object} certificate - * @returns {Boolean} - */ - hasLetsEncryptSslCerts: (certificate) => { - const letsencryptPath = '/data/tls/certbot/live/npm-' + certificate.id; - - return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem'); - }, - - /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts - */ - disableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - const promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); - } - - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('redirection_host', in_use_result.redirection_hosts)); - } - - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs('dead_host', in_use_result.dead_hosts)); - } - - return Promise.all(promises); - } else { - return Promise.resolve(); - } - }, - - /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts - */ - enableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - const promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); - } - - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('redirection_host', in_use_result.redirection_hosts)); - } - - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs('dead_host', in_use_result.dead_hosts)); - } - - return Promise.all(promises); - } else { - return Promise.resolve(); - } - }, - - testHttpsChallenge: async (access, domains) => { - await access.can('certificates:list'); - - if (!isArray(domains)) { - throw new error.InternalValidationError('Domains must be an array of strings'); - } - if (domains.length === 0) { - throw new error.InternalValidationError('No domains provided'); - } - - // Create a test challenge file - const testChallengeDir = '/tmp/acme-challenge/.well-known/acme-challenge'; - const testChallengeFile = testChallengeDir + '/test-challenge'; - fs.mkdirSync(testChallengeDir, { recursive: true }); - fs.writeFileSync(testChallengeFile, 'Success', { encoding: 'utf8' }); - - async function performTestForDomain(domain) { - logger.info('Testing http challenge for ' + domain); - const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; - const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&locationid=10`; - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(formBody), - Connection: 'keep-alive', - 'User-Agent': 'NPMplus', - Accept: '*/*', - }, - }; - - const result = await new Promise((resolve) => { - const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { - let responseBody = ''; - - res.on('data', (chunk) => (responseBody = responseBody + chunk)); - res.on('end', function () { - try { - const parsedBody = JSON.parse(responseBody + ''); - if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); - resolve(undefined); - } else { - resolve(parsedBody); - } - } catch (err) { - if (res.statusCode !== 200) { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`); - } else { - logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`); - } - resolve(undefined); - } - }); - }); - - // Make sure to write the request body. - req.write(formBody); - req.end(); - req.on('error', function (e) { - logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); - resolve(undefined); - }); - }); - - if (!result) { - // Some error occurred while trying to get the data - return 'failed'; - } else if (result.error) { - logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`); - return `other:${result.error.msg}`; - } else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') { - // Server exists and has responded with the correct data - return 'ok'; - } else if (`${result.responsecode}` === '200') { - // Server exists but has responded with wrong data - logger.info(`HTTP challenge test failed for domain ${domain} because of invalid returned data:`, result.htmlresponse); - return 'wrong-data'; - } else if (`${result.responsecode}` === '404') { - // Server exists but responded with a 404 - logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`); - return '404'; - } else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) { - // Server does not exist at domain - logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`); - return 'no-host'; - } else { - // Other errors - logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`); - return `other:${result.responsecode}`; - } - } - - const results = {}; - - for (const domain of domains) { - results[domain] = await performTestForDomain(domain); - } - - // Remove the test challenge file - fs.unlinkSync(testChallengeFile); - - return results; - }, -}; - -module.exports = internalCertificate; diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js deleted file mode 100644 index 0eae1f82f..000000000 --- a/backend/internal/dead-host.js +++ /dev/null @@ -1,450 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const deadHostModel = require('../models/dead_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); - -function omissions() { - return ['is_deleted']; -} - -const internalDeadHost = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('dead_hosts:create', data) - .then((/* access_data */) => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - }) - .then(() => { - // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); - - return deadHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((row) => { - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, data) - .then((cert) => { - // update host with cert id - return internalDeadHost.update(access, { - id: row.id, - certificate_id: cert.id, - }); - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // re-fetch with cert - return internalDeadHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'], - }); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row).then(() => { - return row; - }); - }) - .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'dead-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return row; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @return {Promise} - */ - update: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('dead_hosts:update', data.id) - .then((/* access_data */) => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - } - }) - .then(() => { - return internalDeadHost.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta), - }) - .then((cert) => { - // update host with cert id - data.certificate_id = cert.id; - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign( - {}, - { - domain_names: row.domain_names, - }, - data, - ); - - data = internalHost.cleanSslHstsData(data, row); - - return deadHostModel - .query() - .where({ id: data.id }) - .patch(data) - .then((saved_row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'dead-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return _.omit(saved_row, omissions()); - }); - }); - }) - .then(() => { - return internalDeadHost - .get(access, { - id: data.id, - expand: ['owner', 'certificate'], - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row).then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('dead_hosts:get', data.id) - .then((access_data) => { - const query = deadHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('dead_hosts:delete', data.id) - .then(() => { - return internalDeadHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - return deadHostModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - enable: (access, data) => { - return access - .can('dead_hosts:update', data.id) - .then(() => { - return internalDeadHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'], - }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); - } - - row.enabled = 1; - - return deadHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(deadHostModel, 'dead_host', row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - disable: (access, data) => { - return access - .can('dead_hosts:update', data.id) - .then(() => { - return internalDeadHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); - } - - row.enabled = 0; - - return deadHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 0, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('dead_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'dead-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Hosts - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access - .can('dead_hosts:list') - .then((access_data) => { - const query = deadHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('domain_names', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; - }); - }, - - /** - * Report use - * - * @param {Number} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = deadHostModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, -}; - -module.exports = internalDeadHost; diff --git a/backend/internal/host.js b/backend/internal/host.js deleted file mode 100644 index 6ca5895d2..000000000 --- a/backend/internal/host.js +++ /dev/null @@ -1,217 +0,0 @@ -const _ = require('lodash'); -const proxyHostModel = require('../models/proxy_host'); -const redirectionHostModel = require('../models/redirection_host'); -const deadHostModel = require('../models/dead_host'); - -const internalHost = { - /** - * Makes sure that the ssl_* and hsts_* fields play nicely together. - * ie: if there is no cert, then force_ssl is off. - * if force_ssl is off, then hsts_enabled is definitely off. - * - * @param {object} data - * @param {object} [existing_data] - * @returns {object} - */ - cleanSslHstsData: function (data, existing_data) { - existing_data = existing_data === undefined ? {} : existing_data; - - const combined_data = _.assign({}, existing_data, data); - - if (!combined_data.certificate_id) { - combined_data.ssl_forced = false; - combined_data.hsts_subdomains = false; - } - - if (!combined_data.ssl_forced) { - combined_data.hsts_enabled = false; - } - - return combined_data; - }, - - /** - * used by the getAll functions of hosts, this removes the certificate meta if present - * - * @param {Array} rows - * @returns {Array} - */ - cleanAllRowsCertificateMeta: function (rows) { - rows.map(function (row, idx) { - if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) { - rows[idx].certificate.meta = {}; - } - }); - - return rows; - }, - - /** - * used by the get/update functions of hosts, this removes the certificate meta if present - * - * @param {Object} row - * @returns {Object} - */ - cleanRowCertificateMeta: function (row) { - if (typeof row.certificate !== 'undefined' && row.certificate) { - row.certificate.meta = {}; - } - - return row; - }, - - /** - * This returns all the host types with any domain listed in the provided domain_names array. - * This is used by the certificates to temporarily disable any host that is using the domain - * - * @param {Array} domain_names - * @returns {Promise} - */ - getHostsWithDomains: function (domain_names) { - const promises = [proxyHostModel.query().where('is_deleted', 0), redirectionHostModel.query().where('is_deleted', 0), deadHostModel.query().where('is_deleted', 0)]; - - return Promise.all(promises).then((promises_results) => { - const response_object = { - total_count: 0, - dead_hosts: [], - proxy_hosts: [], - redirection_hosts: [], - }; - - if (promises_results[0]) { - // Proxy Hosts - response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); - response_object.total_count += response_object.proxy_hosts.length; - } - - if (promises_results[1]) { - // Redirection Hosts - response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names); - response_object.total_count += response_object.redirection_hosts.length; - } - - if (promises_results[2]) { - // Dead Hosts - response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); - response_object.total_count += response_object.dead_hosts.length; - } - - return response_object; - }); - }, - - /** - * Internal use only, checks to see if the domain is already taken by any other record - * - * @param {String} hostname - * @param {String} [ignore_type] 'proxy', 'redirection', 'dead' - * @param {Integer} [ignore_id] Must be supplied if type was also supplied - * @returns {Promise} - */ - isHostnameTaken: function (hostname, ignore_type, ignore_id) { - const promises = [ - proxyHostModel - .query() - .where('is_deleted', 0) - .andWhere('domain_names', 'like', '%' + hostname + '%'), - redirectionHostModel - .query() - .where('is_deleted', 0) - .andWhere('domain_names', 'like', '%' + hostname + '%'), - deadHostModel - .query() - .where('is_deleted', 0) - .andWhere('domain_names', 'like', '%' + hostname + '%'), - ]; - - return Promise.all(promises).then((promises_results) => { - let is_taken = false; - - if (promises_results[0]) { - // Proxy Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } - } - - if (promises_results[1]) { - // Redirection Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } - } - - if (promises_results[2]) { - // Dead Hosts - if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) { - is_taken = true; - } - } - - return { - hostname, - is_taken, - }; - }); - }, - - /** - * Private call only - * - * @param {String} hostname - * @param {Array} existing_rows - * @param {Integer} [ignore_id] - * @returns {Boolean} - */ - _checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) { - let is_taken = false; - - if (existing_rows && existing_rows.length) { - existing_rows.map(function (existing_row) { - existing_row.domain_names.map(function (existing_hostname) { - // Does this domain match? - if (existing_hostname.toLowerCase() === hostname.toLowerCase()) { - if (!ignore_id || ignore_id !== existing_row.id) { - is_taken = true; - } - } - }); - }); - } - - return is_taken; - }, - - /** - * Private call only - * - * @param {Array} hosts - * @param {Array} domain_names - * @returns {Array} - */ - _getHostsWithDomains: function (hosts, domain_names) { - const response = []; - - if (hosts && hosts.length) { - hosts.map(function (host) { - let host_matches = false; - - domain_names.map(function (domain_name) { - host.domain_names.map(function (host_domain_name) { - if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) { - host_matches = true; - } - }); - }); - - if (host_matches) { - response.push(host); - } - }); - } - - return response; - }, -}; - -module.exports = internalHost; diff --git a/backend/internal/ip_ranges.js b/backend/internal/ip_ranges.js deleted file mode 100644 index 19b1f6bf3..000000000 --- a/backend/internal/ip_ranges.js +++ /dev/null @@ -1,150 +0,0 @@ -const https = require('https'); -const fs = require('fs'); -const logger = require('../logger').ip_ranges; -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const internalNginx = require('./nginx'); - -const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'; -const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4'; -const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6'; - -const regIpV4 = /^(\d+\.?){4}\/\d+/; -const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/; - -const internalIpRanges = { - interval_timeout: 1000 * 60 * 60 * Number(process.env.IPRT), - interval: null, - interval_processing: false, - iteration_count: 0, - - initTimer: () => { - if (process.env.SKIP_IP_RANGES === 'false') { - logger.info('IP Ranges Renewal Timer initialized'); - internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout); - } - }, - - fetchUrl: (url) => { - return new Promise((resolve, reject) => { - logger.info('Fetching ' + url); - return https - .get(url, (res) => { - res.setEncoding('utf8'); - let raw_data = ''; - res.on('data', (chunk) => { - raw_data += chunk; - }); - - res.on('end', () => { - resolve(raw_data); - }); - }) - .on('error', (err) => { - reject(err); - }); - }); - }, - - /** - * Triggered at startup and then later by a timer, this will fetch the ip ranges from services and apply them to nginx. - */ - fetch: () => { - if (!internalIpRanges.interval_processing && process.env.SKIP_IP_RANGES === 'false') { - internalIpRanges.interval_processing = true; - logger.info('Fetching IP Ranges from online services...'); - - let ip_ranges = []; - - return internalIpRanges - .fetchUrl(CLOUDFRONT_URL) - .then((cloudfront_data) => { - const data = JSON.parse(cloudfront_data); - - if (data && typeof data.prefixes !== 'undefined') { - data.prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { - ip_ranges.push(item.ip_prefix); - } - }); - } - - if (data && typeof data.ipv6_prefixes !== 'undefined') { - data.ipv6_prefixes.map((item) => { - if (item.service === 'CLOUDFRONT') { - ip_ranges.push(item.ipv6_prefix); - } - }); - } - }) - .then(() => { - return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL); - }) - .then((cloudfare_data) => { - const items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line)); - ip_ranges = [...ip_ranges, ...items]; - }) - .then(() => { - return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL); - }) - .then((cloudfare_data) => { - const items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line)); - ip_ranges = [...ip_ranges, ...items]; - }) - .then(() => { - const clean_ip_ranges = []; - ip_ranges.map((range) => { - if (range) { - clean_ip_ranges.push(range); - } - }); - - return internalIpRanges.generateConfig(clean_ip_ranges).then(() => { - if (internalIpRanges.iteration_count) { - // Reload nginx - return internalNginx.reload(); - } - }); - }) - .then(() => { - internalIpRanges.interval_processing = false; - internalIpRanges.iteration_count++; - }) - .catch((err) => { - logger.error(err.message); - internalIpRanges.interval_processing = false; - }); - } - }, - - /** - * @param {Array} ip_ranges - * @returns {Promise} - */ - generateConfig: (ip_ranges) => { - const renderEngine = utils.getRenderEngine(); - return new Promise((resolve, reject) => { - let template = null; - const filename = '/data/nginx/ip_ranges.conf'; - try { - template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', { encoding: 'utf8' }); - } catch (err) { - reject(new error.ConfigurationError(err.message)); - return; - } - - renderEngine - .parseAndRender(template, { ip_ranges }) - .then((config_text) => { - fs.writeFileSync(filename, config_text, { encoding: 'utf8' }); - resolve(true); - }) - .catch((err) => { - logger.warn('Could not write ' + filename + ':', err.message); - reject(new error.ConfigurationError(err.message)); - }); - }); - }, -}; - -module.exports = internalIpRanges; diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js deleted file mode 100644 index 5483f2d8f..000000000 --- a/backend/internal/nginx.js +++ /dev/null @@ -1,376 +0,0 @@ -const _ = require('lodash'); -const fs = require('fs'); -const logger = require('../logger').nginx; -const config = require('../lib/config'); -const utils = require('../lib/utils'); -const error = require('../lib/error'); - -const NgxPidFilePath = '/usr/local/nginx/logs/nginx.pid'; - -const internalNginx = { - /** - * This will: - * - test the nginx config first to make sure it's OK - * - create / recreate the config for the host - * - test again - * - IF OK: update the meta with online status - * - IF BAD: update the meta with offline status and remove the config entirely - * - then reload nginx - * - * @param {Object|String} model - * @param {String} host_type - * @param {Object} host - * @returns {Promise} - */ - configure: (model, host_type, host) => { - let combined_meta = {}; - - return internalNginx - .test() - .then(() => { - // Nginx is OK - // We're deleting this config regardless. - // Don't throw errors, as the file may not exist at all - // Delete the .err file too - return internalNginx.deleteConfig(host_type, host, false, true); - }) - .then(() => { - return internalNginx.generateConfig(host_type, host); - }) - .then(() => { - // Test nginx again and update meta with result - return internalNginx - .test() - .then(() => { - // nginx is ok - combined_meta = _.assign({}, host.meta, { - nginx_online: true, - nginx_err: null, - }); - - return model.query().where('id', host.id).patch({ - meta: combined_meta, - }); - }) - .catch((err) => { - // Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported. - // It will always look like this: - // nginx: [alert] could not open error log file: open() "/dev/null" failed (6: No such device or address) - - const valid_lines = []; - const err_lines = err.message.split('\n'); - err_lines.map(function (line) { - if (line.indexOf('/dev/null') === -1) { - valid_lines.push(line); - } - }); - - if (config.debug()) { - logger.error('Nginx test failed:', valid_lines.join('\n')); - } - - // config is bad, update meta and delete config - combined_meta = _.assign({}, host.meta, { - nginx_online: false, - nginx_err: valid_lines.join('\n'), - }); - - return model - .query() - .where('id', host.id) - .patch({ - meta: combined_meta, - }) - .then(() => { - internalNginx.renameConfigAsError(host_type, host); - }) - .then(() => { - return internalNginx.deleteConfig(host_type, host, true); - }); - }); - }) - .then(() => { - return internalNginx.reload(); - }) - .then(() => { - return combined_meta; - }); - }, - - /** - * @returns {Promise} - */ - test: () => { - if (config.debug()) { - logger.info('Testing Nginx configuration'); - } - - return utils.exec('nginx -tq'); - }, - - /** - * @returns {Promise} - */ - - reload: () => { - return internalNginx.test().then(() => { - if (fs.existsSync(NgxPidFilePath)) { - const ngxPID = fs.readFileSync(NgxPidFilePath, 'utf8').trim(); - if (ngxPID.length > 0) { - logger.info('Reloading Nginx'); - utils.exec('nginx -s reload'); - } else { - logger.info('Starting Nginx'); - utils.execfg('nginx -e stderr'); - } - } else { - logger.info('Starting Nginx'); - utils.execfg('nginx -e stderr'); - } - }); - }, - - /** - * @param {String} host_type - * @param {Integer} host_id - * @returns {String} - */ - getConfigName: (host_type, host_id) => { - if (host_type === 'default') { - return '/data/nginx/default.conf'; - } - return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; - }, - - /** - * Generates custom locations - * @param {Object} host - * @returns {Promise} - */ - renderLocations: (host) => { - return new Promise((resolve, reject) => { - let template; - - try { - template = fs.readFileSync(__dirname + '/../templates/_location.conf', { encoding: 'utf8' }); - } catch (err) { - reject(new error.ConfigurationError(err.message)); - return; - } - - const renderEngine = utils.getRenderEngine(); - let renderedLocations = ''; - - const locationRendering = async () => { - for (let i = 0; i < host.locations.length; i++) { - const locationCopy = Object.assign({}, { access_list_id: host.access_list_id }, { certificate_id: host.certificate_id }, { ssl_forced: host.ssl_forced }, { caching_enabled: host.caching_enabled }, { block_exploits: host.block_exploits }, { allow_websocket_upgrade: host.allow_websocket_upgrade }, { http2_support: host.http2_support }, { hsts_enabled: host.hsts_enabled }, { hsts_subdomains: host.hsts_subdomains }, { access_list: host.access_list }, { certificate: host.certificate }, host.locations[i]); - - if (locationCopy.forward_host.indexOf('/') > -1) { - const split = locationCopy.forward_host.split('/'); - - locationCopy.forward_host = split.shift(); - locationCopy.forward_path = `/${split.join('/')}`; - } - - renderedLocations += await renderEngine.parseAndRender(template, locationCopy); - } - }; - - locationRendering().then(() => resolve(renderedLocations)); - }); - }, - - /** - * @param {String} host_type - * @param {Object} host - * @returns {Promise} - */ - generateConfig: (host_type, host) => { - const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); - - if (config.debug()) { - logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); - } - - const renderEngine = utils.getRenderEngine(); - - return new Promise((resolve, reject) => { - let template = null; - const filename = internalNginx.getConfigName(nice_host_type, host.id); - - try { - template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', { encoding: 'utf8' }); - } catch (err) { - reject(new error.ConfigurationError(err.message)); - return; - } - - let locationsPromise; - let origLocations; - - // Manipulate the data a bit before sending it to the template - if (nice_host_type !== 'default') { - host.use_default_location = true; - if (typeof host.advanced_config !== 'undefined' && host.advanced_config) { - host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config); - } - } - - if (host.locations) { - // logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2)); - origLocations = [].concat(host.locations); - locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => { - host.locations = renderedLocations; - }); - - // Allow someone who is using / custom location path to use it, and skip the default / location - _.map(host.locations, (location) => { - if (location.path === '/') { - host.use_default_location = false; - } - }); - } else { - locationsPromise = Promise.resolve(); - } - - // Set the IPv6 setting for the host - host.ipv6 = internalNginx.ipv6Enabled(); - - locationsPromise.then(() => { - renderEngine - .parseAndRender(template, host) - .then((config_text) => { - fs.writeFileSync(filename, config_text, { encoding: 'utf8' }); - - if (config.debug()) { - logger.success('Wrote config:', filename, config_text); - } - - // Restore locations array - host.locations = origLocations; - - resolve(true); - }) - .catch((err) => { - if (config.debug()) { - logger.warn('Could not write ' + filename + ':', err.message); - } - - reject(new error.ConfigurationError(err.message)); - }); - }); - }); - }, - - /** - * A simple wrapper around unlinkSync that writes to the logger - * - * @param {String} filename - */ - deleteFile: (filename) => { - logger.debug('Deleting file: ' + filename); - try { - fs.unlinkSync(filename); - } catch (err) { - logger.debug('Could not delete file:', JSON.stringify(err, null, 2)); - } - }, - - /** - * - * @param {String} host_type - * @returns String - */ - getFileFriendlyHostType: (host_type) => { - return host_type.replace(new RegExp('-', 'g'), '_'); - }, - - /** - * @param {String} host_type - * @param {Object} [host] - * @param {Boolean} [delete_err_file] - * @returns {Promise} - */ - deleteConfig: (host_type, host, delete_err_file) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; - - return new Promise((resolve /*, reject */) => { - internalNginx.deleteFile(config_file); - if (delete_err_file) { - internalNginx.deleteFile(config_file_err); - } - resolve(); - }); - }, - - /** - * @param {String} host_type - * @param {Object} [host] - * @returns {Promise} - */ - renameConfigAsError: (host_type, host) => { - const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); - const config_file_err = config_file + '.err'; - - return new Promise((resolve /*, reject */) => { - fs.unlink(config_file, () => { - // ignore result, continue - fs.rename(config_file, config_file_err, () => { - // also ignore result, as this is a debugging informative file anyway - resolve(); - }); - }); - }); - }, - - /** - * @param {String} host_type - * @param {Array} hosts - * @returns {Promise} - */ - bulkGenerateConfigs: (host_type, hosts) => { - const promises = []; - hosts.map(function (host) { - promises.push(internalNginx.generateConfig(host_type, host)); - }); - - return Promise.all(promises); - }, - - /** - * @param {String} host_type - * @param {Array} hosts - * @returns {Promise} - */ - bulkDeleteConfigs: (host_type, hosts) => { - const promises = []; - hosts.map(function (host) { - promises.push(internalNginx.deleteConfig(host_type, host, true)); - }); - - return Promise.all(promises); - }, - - /** - * @param {string} config - * @returns {boolean} - */ - advancedConfigHasDefaultLocation: function (cfg) { - return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); - }, - - /** - * @returns {boolean} - */ - ipv6Enabled: function () { - if (typeof process.env.DISABLE_IPV6 !== 'undefined') { - const disabled = process.env.DISABLE_IPV6.toLowerCase(); - return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); - } - - return true; - }, -}; - -module.exports = internalNginx; diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js deleted file mode 100644 index f496b06ce..000000000 --- a/backend/internal/proxy-host.js +++ /dev/null @@ -1,457 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const proxyHostModel = require('../models/proxy_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); - -function omissions() { - return ['is_deleted']; -} - -const internalProxyHost = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('proxy_hosts:create', data) - .then(() => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - }) - .then(() => { - // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); - - return proxyHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((row) => { - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, data) - .then((cert) => { - // update host with cert id - return internalProxyHost.update(access, { - id: row.id, - certificate_id: cert.id, - }); - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // re-fetch with cert - return internalProxyHost.get(access, { - id: row.id, - expand: ['certificate', 'owner', 'access_list.[clients,items]'], - }); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row).then(() => { - return row; - }); - }) - .then((row) => { - // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'proxy-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return row; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @return {Promise} - */ - update: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('proxy_hosts:update', data.id) - .then((/* access_data */) => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - } - }) - .then(() => { - return internalProxyHost.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta), - }) - .then((cert) => { - // update host with cert id - data.certificate_id = cert.id; - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign( - {}, - { - domain_names: row.domain_names, - }, - data, - ); - - data = internalHost.cleanSslHstsData(data, row); - - return proxyHostModel - .query() - .where({ id: data.id }) - .patch(data) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'proxy-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return saved_row; - }); - }); - }) - .then(() => { - return internalProxyHost - .get(access, { - id: data.id, - expand: ['owner', 'certificate', 'access_list.[clients,items]'], - }) - .then((row) => { - if (!row.enabled) { - // No need to add nginx config if host is disabled - return row; - } - // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row).then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('proxy_hosts:get', data.id) - .then((access_data) => { - const query = proxyHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,access_list.[clients,items],certificate]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - row = internalHost.cleanRowCertificateMeta(row); - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('proxy_hosts:delete', data.id) - .then(() => { - return internalProxyHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - return proxyHostModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - enable: (access, data) => { - return access - .can('proxy_hosts:update', data.id) - .then(() => { - return internalProxyHost.get(access, { - id: data.id, - expand: ['certificate', 'owner', 'access_list'], - }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); - } - - row.enabled = 1; - - return proxyHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(proxyHostModel, 'proxy_host', row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - disable: (access, data) => { - return access - .can('proxy_hosts:update', data.id) - .then(() => { - return internalProxyHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); - } - - row.enabled = 0; - - return proxyHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 0, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('proxy_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'proxy-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Hosts - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access - .can('proxy_hosts:list') - .then((access_data) => { - const query = proxyHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,access_list,certificate]').orderBy('domain_names', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('domain_names', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; - }); - }, - - /** - * Report use - * - * @param {Number} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = proxyHostModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, -}; - -module.exports = internalProxyHost; diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js deleted file mode 100644 index 971fc0e53..000000000 --- a/backend/internal/redirection-host.js +++ /dev/null @@ -1,450 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const redirectionHostModel = require('../models/redirection_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); -const internalCertificate = require('./certificate'); - -function omissions() { - return ['is_deleted']; -} - -const internalRedirectionHost = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('redirection_hosts:create', data) - .then((/* access_data */) => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - }) - .then(() => { - // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - data = internalHost.cleanSslHstsData(data); - - return redirectionHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((row) => { - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, data) - .then((cert) => { - // update host with cert id - return internalRedirectionHost.update(access, { - id: row.id, - certificate_id: cert.id, - }); - }) - .then(() => { - return row; - }); - } - return row; - }) - .then((row) => { - // re-fetch with cert - return internalRedirectionHost.get(access, { - id: row.id, - expand: ['certificate', 'owner'], - }); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then(() => { - return row; - }); - }) - .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'redirection-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return row; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @return {Promise} - */ - update: (access, data) => { - const create_certificate = data.certificate_id === 'new'; - - if (create_certificate) { - delete data.certificate_id; - } - - return access - .can('redirection_hosts:update', data.id) - .then((/* access_data */) => { - // Get a list of the domain names and check each of them against existing records - const domain_name_check_promises = []; - - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id)); - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); - }); - } - }) - .then(() => { - return internalRedirectionHost.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - if (create_certificate) { - return internalCertificate - .createQuickCertificate(access, { - domain_names: data.domain_names || row.domain_names, - meta: _.assign({}, row.meta, data.meta), - }) - .then((cert) => { - // update host with cert id - data.certificate_id = cert.id; - }) - .then(() => { - return row; - }); - } else { - return row; - } - }) - .then((row) => { - // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - data = _.assign( - {}, - { - domain_names: row.domain_names, - }, - data, - ); - - data = internalHost.cleanSslHstsData(data, row); - - return redirectionHostModel - .query() - .where({ id: data.id }) - .patch(data) - .then((saved_row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'redirection-host', - object_id: row.id, - meta: data, - }) - .then(() => { - return _.omit(saved_row, omissions()); - }); - }); - }) - .then(() => { - return internalRedirectionHost - .get(access, { - id: data.id, - expand: ['owner', 'certificate'], - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then((new_meta) => { - row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); - return _.omit(row, omissions()); - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('redirection_hosts:get', data.id) - .then((access_data) => { - const query = redirectionHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - row = internalHost.cleanRowCertificateMeta(row); - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('redirection_hosts:delete', data.id) - .then(() => { - return internalRedirectionHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - return redirectionHostModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - enable: (access, data) => { - return access - .can('redirection_hosts:update', data.id) - .then(() => { - return internalRedirectionHost.get(access, { - id: data.id, - expand: ['certificate', 'owner'], - }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); - } - - row.enabled = 1; - - return redirectionHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(redirectionHostModel, 'redirection_host', row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - disable: (access, data) => { - return access - .can('redirection_hosts:update', data.id) - .then(() => { - return internalRedirectionHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); - } - - row.enabled = 0; - - return redirectionHostModel - .query() - .where('id', row.id) - .patch({ - enabled: 0, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('redirection_host', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'redirection-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Hosts - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access - .can('redirection_hosts:list') - .then((access_data) => { - const query = redirectionHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('domain_names', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; - }); - }, - - /** - * Report use - * - * @param {Number} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = redirectionHostModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, -}; - -module.exports = internalRedirectionHost; diff --git a/backend/internal/report.js b/backend/internal/report.js deleted file mode 100644 index 9eda4cf50..000000000 --- a/backend/internal/report.js +++ /dev/null @@ -1,32 +0,0 @@ -const internalProxyHost = require('./proxy-host'); -const internalRedirectionHost = require('./redirection-host'); -const internalDeadHost = require('./dead-host'); -const internalStream = require('./stream'); - -const internalReport = { - /** - * @param {Access} access - * @return {Promise} - */ - getHostsReport: (access) => { - return access - .can('reports:hosts', 1) - .then((access_data) => { - const user_id = access.token.getUserId(1); - - const promises = [internalProxyHost.getCount(user_id, access_data.visibility), internalRedirectionHost.getCount(user_id, access_data.visibility), internalStream.getCount(user_id, access_data.visibility), internalDeadHost.getCount(user_id, access_data.visibility)]; - - return Promise.all(promises); - }) - .then((counts) => { - return { - proxy: counts.shift(), - redirection: counts.shift(), - stream: counts.shift(), - dead: counts.shift(), - }; - }); - }, -}; - -module.exports = internalReport; diff --git a/backend/internal/setting.js b/backend/internal/setting.js deleted file mode 100644 index c3fc4a1af..000000000 --- a/backend/internal/setting.js +++ /dev/null @@ -1,125 +0,0 @@ -const fs = require('fs'); -const error = require('../lib/error'); -const settingModel = require('../models/setting'); -const internalNginx = require('./nginx'); - -const internalSetting = { - /** - * @param {Access} access - * @param {Object} data - * @param {String} data.id - * @return {Promise} - */ - update: (access, data) => { - return access - .can('settings:update', data.id) - .then((/* access_data */) => { - return internalSetting.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - return settingModel.query().where({ id: data.id }).patch(data); - }) - .then(() => { - return internalSetting.get(access, { - id: data.id, - }); - }) - .then((row) => { - if (row.id === 'default-site') { - // write the html if we need to - if (row.value === 'html') { - fs.writeFileSync('/data/nginx/etc/index.html', row.meta.html, { encoding: 'utf8' }); - } - - // Configure nginx - return internalNginx - .deleteConfig('default') - .then(() => { - return internalNginx.generateConfig('default', row); - }) - .then(() => { - return internalNginx.test(); - }) - .then(() => { - return internalNginx.reload(); - }) - .then(() => { - return row; - }) - .catch((/* err */) => { - internalNginx - .deleteConfig('default') - .then(() => { - return internalNginx.test(); - }) - .then(() => { - return internalNginx.reload(); - }) - .then(() => { - // I'm being slack here I know.. - throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.'); - }); - }); - } else { - return row; - } - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {String} data.id - * @return {Promise} - */ - get: (access, data) => { - return access - .can('settings:get', data.id) - .then(() => { - return settingModel.query().where('id', data.id).first(); - }) - .then((row) => { - if (row) { - return row; - } else { - throw new error.ItemNotFoundError(data.id); - } - }); - }, - - /** - * This will only count the settings - * - * @param {Access} access - * @returns {*} - */ - getCount: (access) => { - return access - .can('settings:list') - .then(() => { - return settingModel.query().count('id as count').first(); - }) - .then((row) => { - return parseInt(row.count, 10); - }); - }, - - /** - * All settings - * - * @param {Access} access - * @returns {Promise} - */ - getAll: (access) => { - return access.can('settings:list').then(() => { - return settingModel.query().orderBy('description', 'ASC'); - }); - }, -}; - -module.exports = internalSetting; diff --git a/backend/internal/stream.js b/backend/internal/stream.js deleted file mode 100644 index 5b0413713..000000000 --- a/backend/internal/stream.js +++ /dev/null @@ -1,331 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const streamModel = require('../models/stream'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); - -function omissions() { - return ['is_deleted']; -} - -const internalStream = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - return access - .can('streams:create', data) - .then((/* access_data */) => { - // TODO: At this point the existing ports should have been checked - data.owner_user_id = access.token.getUserId(1); - - if (typeof data.meta === 'undefined') { - data.meta = {}; - } - - return streamModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(streamModel, 'stream', row).then(() => { - return internalStream.get(access, { id: row.id, expand: ['owner'] }); - }); - }) - .then((row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'stream', - object_id: row.id, - meta: data, - }) - .then(() => { - return row; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @return {Promise} - */ - update: (access, data) => { - return access - .can('streams:update', data.id) - .then((/* access_data */) => { - // TODO: at this point the existing streams should have been checked - return internalStream.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); - } - - return streamModel - .query() - .patchAndFetchById(row.id, data) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - return internalNginx.configure(streamModel, 'stream', saved_row).then(() => { - return internalStream.get(access, { id: row.id, expand: ['owner'] }); - }); - }) - .then((saved_row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'stream', - object_id: row.id, - meta: data, - }) - .then(() => { - return saved_row; - }); - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - return access - .can('streams:get', data.id) - .then((access_data) => { - const query = streamModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first(); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('streams:delete', data.id) - .then(() => { - return internalStream.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - - return streamModel - .query() - .where('id', row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('stream', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - enable: (access, data) => { - return access - .can('streams:update', data.id) - .then(() => { - return internalStream.get(access, { - id: data.id, - expand: ['owner'], - }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (row.enabled) { - throw new error.ValidationError('Host is already enabled'); - } - - row.enabled = 1; - - return streamModel - .query() - .where('id', row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(streamModel, 'stream', row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'enabled', - object_type: 'stream', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Number} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - disable: (access, data) => { - return access - .can('streams:update', data.id) - .then(() => { - return internalStream.get(access, { id: data.id }); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } else if (!row.enabled) { - throw new error.ValidationError('Host is already disabled'); - } - - row.enabled = 0; - - return streamModel - .query() - .where('id', row.id) - .patch({ - enabled: 0, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig('stream', row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'disabled', - object_type: 'stream-host', - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * All Streams - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access.can('streams:list').then((access_data) => { - const query = streamModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('incoming_port', 'ASC'); - - if (access_data.permission_visibility !== 'all') { - query.andWhere('owner_user_id', access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('incoming_port', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }); - }, - - /** - * Report use - * - * @param {Number} user_id - * @param {String} visibility - * @returns {Promise} - */ - getCount: (user_id, visibility) => { - const query = streamModel.query().count('id as count').where('is_deleted', 0); - - if (visibility !== 'all') { - query.andWhere('owner_user_id', user_id); - } - - return query.first().then((row) => { - return parseInt(row.count, 10); - }); - }, -}; - -module.exports = internalStream; diff --git a/backend/internal/token.js b/backend/internal/token.js deleted file mode 100644 index 003943700..000000000 --- a/backend/internal/token.js +++ /dev/null @@ -1,155 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const userModel = require('../models/user'); -const authModel = require('../models/auth'); -const helpers = require('../lib/helpers'); -const TokenModel = require('../models/token'); - -module.exports = { - /** - * @param {Object} data - * @param {String} data.identity - * @param {String} data.secret - * @param {String} [data.scope] - * @param {String} [data.expiry] - * @param {String} [issuer] - * @returns {Promise} - */ - getTokenFromEmail: (data, issuer) => { - const Token = new TokenModel(); - - data.scope = data.scope || 'user'; - data.expiry = data.expiry || '1d'; - - return userModel - .query() - .where('email', data.identity.toLowerCase().trim()) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) - .first() - .then((user) => { - if (user) { - // Get auth - return authModel - .query() - .where('user_id', '=', user.id) - .where('type', '=', 'password') - .first() - .then((auth) => { - if (auth) { - return auth.verifyPassword(data.secret).then((valid) => { - if (valid) { - if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) { - // The scope requested doesn't exist as a role against the user, - // you shall not pass. - throw new error.AuthError('Invalid scope: ' + data.scope); - } - - // Create a moment of the expiry expression - const expiry = helpers.parseDatePeriod(data.expiry); - if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); - } - - return Token.create({ - iss: issuer || 'api', - attrs: { - id: user.id, - }, - scope: [data.scope], - expiresIn: data.expiry, - }).then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - }; - }); - } else { - throw new error.AuthError('Invalid password'); - } - }); - } else { - throw new error.AuthError('No password auth for user'); - } - }); - } else { - throw new error.AuthError('No relevant user found'); - } - }); - }, - - /** - * @param {Access} access - * @param {Object} [data] - * @param {String} [data.expiry] - * @param {String} [data.scope] Only considered if existing token scope is admin - * @returns {Promise} - */ - getFreshToken: (access, data) => { - const Token = new TokenModel(); - - data = data || {}; - data.expiry = data.expiry || '1d'; - - if (access && access.token.getUserId(0)) { - // Create a moment of the expiry expression - const expiry = helpers.parseDatePeriod(data.expiry); - if (expiry === null) { - throw new error.AuthError('Invalid expiry time: ' + data.expiry); - } - - const token_attrs = { - id: access.token.getUserId(0), - }; - - // Only admins can request otherwise scoped tokens - let scope = access.token.get('scope'); - if (data.scope && access.token.hasScope('admin')) { - scope = [data.scope]; - - if (data.scope === 'job-board' || data.scope === 'worker') { - token_attrs.id = 0; - } - } - - return Token.create({ - iss: 'api', - scope, - attrs: token_attrs, - expiresIn: data.expiry, - }).then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - }; - }); - } else { - throw new error.AssertionFailedError('Existing token contained invalid user data'); - } - }, - - /** - * @param {Object} user - * @returns {Promise} - */ - getTokenFromUser: (user) => { - const expire = '1d'; - const Token = new TokenModel(); - const expiry = helpers.parseDatePeriod(expire); - - return Token.create({ - iss: 'api', - attrs: { - id: user.id, - }, - scope: ['user'], - expiresIn: expire, - }).then((signed) => { - return { - token: signed.token, - expires: expiry.toISOString(), - user, - }; - }); - }, -}; diff --git a/backend/internal/user.js b/backend/internal/user.js deleted file mode 100644 index 0992b22db..000000000 --- a/backend/internal/user.js +++ /dev/null @@ -1,482 +0,0 @@ -const _ = require('lodash'); -const error = require('../lib/error'); -const utils = require('../lib/utils'); -const userModel = require('../models/user'); -const userPermissionModel = require('../models/user_permission'); -const authModel = require('../models/auth'); -const gravatar = require('gravatar'); -const internalToken = require('./token'); -const internalAuditLog = require('./audit-log'); - -function omissions() { - return ['is_deleted']; -} - -const internalUser = { - /** - * @param {Access} access - * @param {Object} data - * @returns {Promise} - */ - create: (access, data) => { - const auth = data.auth || null; - delete data.auth; - - data.avatar = data.avatar || ''; - data.roles = data.roles || []; - - if (typeof data.is_disabled !== 'undefined') { - data.is_disabled = data.is_disabled ? 1 : 0; - } - - return access - .can('users:create', data) - .then(() => { - data.avatar = gravatar.url(data.email, { default: 'mm' }); - - return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((user) => { - if (auth) { - return authModel - .query() - .insert({ - user_id: user.id, - type: auth.type, - secret: auth.secret, - meta: {}, - }) - .then(() => { - return user; - }); - } else { - return user; - } - }) - .then((user) => { - // Create permissions row as well - const is_admin = data.roles.indexOf('admin') !== -1; - - return userPermissionModel - .query() - .insert({ - user_id: user.id, - visibility: is_admin ? 'all' : 'user', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage', - }) - .then(() => { - return internalUser.get(access, { id: user.id, expand: ['permissions'] }); - }); - }) - .then((user) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'created', - object_type: 'user', - object_id: user.id, - meta: user, - }) - .then(() => { - return user; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {String} [data.email] - * @param {String} [data.name] - * @return {Promise} - */ - update: (access, data) => { - if (typeof data.is_disabled !== 'undefined') { - data.is_disabled = data.is_disabled ? 1 : 0; - } - - return access - .can('users:update', data.id) - .then(() => { - // Make sure that the user being updated doesn't change their email to another user that is already using it - // 1. get user we want to update - return internalUser.get(access, { id: data.id }).then((user) => { - // 2. if email is to be changed, find other users with that email - if (typeof data.email !== 'undefined') { - data.email = data.email.toLowerCase().trim(); - - if (user.email !== data.email) { - return internalUser.isEmailAvailable(data.email, data.id).then((available) => { - if (!available) { - throw new error.ValidationError('Email address already in use - ' + data.email); - } - - return user; - }); - } - } - - // No change to email: - return user; - }); - }) - .then((user) => { - if (user.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); - } - - data.avatar = gravatar.url(data.email || user.email, { default: 'mm' }); - - return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions())); - }) - .then(() => { - return internalUser.get(access, { id: data.id }); - }) - .then((user) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: data, - }) - .then(() => { - return user; - }); - }); - }, - - /** - * @param {Access} access - * @param {Object} [data] - * @param {Integer} [data.id] Defaults to the token user - * @param {Array} [data.expand] - * @param {Array} [data.omit] - * @return {Promise} - */ - get: (access, data) => { - if (typeof data === 'undefined') { - data = {}; - } - - if (typeof data.id === 'undefined' || !data.id) { - data.id = access.token.getUserId(0); - } - - return access - .can('users:get', data.id) - .then(() => { - const query = userModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[permissions]').first(); - - if (typeof data.expand !== 'undefined' && data.expand !== null) { - query.withGraphFetched('[' + data.expand.join(', ') + ']'); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row) { - throw new error.ItemNotFoundError(data.id); - } - // Custom omissions - if (typeof data.omit !== 'undefined' && data.omit !== null) { - row = _.omit(row, data.omit); - } - return row; - }); - }, - - /** - * Checks if an email address is available, but if a user_id is supplied, it will ignore checking - * against that user. - * - * @param email - * @param user_id - */ - isEmailAvailable: (email, user_id) => { - const query = userModel.query().where('email', '=', email.toLowerCase().trim()).where('is_deleted', 0).first(); - - if (typeof user_id !== 'undefined') { - query.where('id', '!=', user_id); - } - - return query.then((user) => { - return !user; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {String} [data.reason] - * @returns {Promise} - */ - delete: (access, data) => { - return access - .can('users:delete', data.id) - .then(() => { - return internalUser.get(access, { id: data.id }); - }) - .then((user) => { - if (!user) { - throw new error.ItemNotFoundError(data.id); - } - - // Make sure user can't delete themselves - if (user.id === access.token.getUserId(0)) { - throw new error.PermissionError('You cannot delete yourself.'); - } - - return userModel - .query() - .where('id', user.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: 'deleted', - object_type: 'user', - object_id: user.id, - meta: _.omit(user, omissions()), - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * This will only count the users - * - * @param {Access} access - * @param {String} [search_query] - * @returns {*} - */ - getCount: (access, search_query) => { - return access - .can('users:list') - .then(() => { - const query = userModel.query().count('id as count').where('is_deleted', 0).first(); - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('user.name', 'like', '%' + search_query + '%').orWhere('user.email', 'like', '%' + search_query + '%'); - }); - } - - return query; - }) - .then((row) => { - return parseInt(row.count, 10); - }); - }, - - /** - * All users - * - * @param {Access} access - * @param {Array} [expand] - * @param {String} [search_query] - * @returns {Promise} - */ - getAll: (access, expand, search_query) => { - return access.can('users:list').then(() => { - const query = userModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[permissions]').orderBy('name', 'ASC'); - - // Query is used for searching - if (typeof search_query === 'string') { - query.where(function () { - this.where('name', 'like', '%' + search_query + '%').orWhere('email', 'like', '%' + search_query + '%'); - }); - } - - if (typeof expand !== 'undefined' && expand !== null) { - query.withGraphFetched('[' + expand.join(', ') + ']'); - } - - return query.then(utils.omitRows(omissions())); - }); - }, - - /** - * @param {Access} access - * @param {Integer} [id_requested] - * @returns {[String]} - */ - getUserOmisionsByAccess: (access, id_requested) => { - let response = []; // Admin response - - if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) { - response = ['roles', 'is_deleted']; // Restricted response - } - - return response; - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - * @param {String} data.type - * @param {String} data.secret - * @return {Promise} - */ - setPassword: (access, data) => { - return access - .can('users:password', data.id) - .then(() => { - return internalUser.get(access, { id: data.id }); - }) - .then((user) => { - if (user.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); - } - - if (user.id === access.token.getUserId(0)) { - // they're setting their own password. Make sure their current password is correct - if (typeof data.current === 'undefined' || !data.current) { - throw new error.ValidationError('Current password was not supplied'); - } - - return internalToken - .getTokenFromEmail({ - identity: user.email, - secret: data.current, - }) - .then(() => { - return user; - }); - } - - return user; - }) - .then((user) => { - // Get auth, patch if it exists - return authModel - .query() - .where('user_id', user.id) - .andWhere('type', data.type) - .first() - .then((existing_auth) => { - if (existing_auth) { - // patch - return authModel.query().where('user_id', user.id).andWhere('type', data.type).patch({ - type: data.type, // This is required for the model to encrypt on save - secret: data.secret, - }); - } else { - // insert - return authModel.query().insert({ - user_id: user.id, - type: data.type, - secret: data.secret, - meta: {}, - }); - } - }) - .then(() => { - // Add to Audit Log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, - password_changed: true, - auth_type: data.type, - }, - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @return {Promise} - */ - setPermissions: (access, data) => { - return access - .can('users:permissions', data.id) - .then(() => { - return internalUser.get(access, { id: data.id }); - }) - .then((user) => { - if (user.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); - } - - return user; - }) - .then((user) => { - // Get perms row, patch if it exists - return userPermissionModel - .query() - .where('user_id', user.id) - .first() - .then((existing_auth) => { - if (existing_auth) { - // patch - return userPermissionModel - .query() - .where('user_id', user.id) - .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data)); - } else { - // insert - return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data)); - } - }) - .then((permissions) => { - // Add to Audit Log - return internalAuditLog.add(access, { - action: 'updated', - object_type: 'user', - object_id: user.id, - meta: { - name: user.name, - permissions, - }, - }); - }); - }) - .then(() => { - return true; - }); - }, - - /** - * @param {Access} access - * @param {Object} data - * @param {Integer} data.id - */ - loginAs: (access, data) => { - return access - .can('users:loginas', data.id) - .then(() => { - return internalUser.get(access, data); - }) - .then((user) => { - return internalToken.getTokenFromUser(user); - }); - }, -}; - -module.exports = internalUser; diff --git a/backend/knexfile.js b/backend/knexfile.js deleted file mode 100644 index 81de3ed25..000000000 --- a/backend/knexfile.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - development: { - client: 'mysql', - migrations: { - tableName: 'migrations', - stub: 'lib/migrate_template.js', - directory: 'migrations', - }, - }, - - production: { - client: 'mysql', - migrations: { - tableName: 'migrations', - stub: 'lib/migrate_template.js', - directory: 'migrations', - }, - }, -}; diff --git a/backend/lib/access.js b/backend/lib/access.js deleted file mode 100644 index fe5715bf2..000000000 --- a/backend/lib/access.js +++ /dev/null @@ -1,300 +0,0 @@ -/** - * Some Notes: This is a friggin complicated piece of code. - * - * "scope" in this file means "where did this token come from and what is using it", so 99% of the time - * the "scope" is going to be "user" because it would be a user token. This is not to be confused with - * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. - * - * - */ - -const _ = require('lodash'); -const logger = require('../logger').access; -const validator = require('ajv'); -const error = require('./error'); -const userModel = require('../models/user'); -const proxyHostModel = require('../models/proxy_host'); -const TokenModel = require('../models/token'); -const roleSchema = require('./access/roles.json'); -const permsSchema = require('./access/permissions.json'); - -module.exports = function (token_string) { - const Token = new TokenModel(); - let token_data = null; - let initialized = false; - const object_cache = {}; - let allow_internal_access = false; - let user_roles = []; - let permissions = {}; - - /** - * Loads the Token object from the token string - * - * @returns {Promise} - */ - this.init = () => { - return new Promise((resolve, reject) => { - if (initialized) { - resolve(); - } else if (!token_string) { - reject(new error.PermissionError('Permission Denied')); - } else { - resolve( - Token.load(token_string).then((data) => { - token_data = data; - - // At this point we need to load the user from the DB and make sure they: - // - exist (and not soft deleted) - // - still have the appropriate scopes for this token - // This is only required when the User ID is supplied or if the token scope has `user` - - if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) { - // Has token user id or token user scope - return userModel - .query() - .where('id', token_data.attrs.id) - .andWhere('is_deleted', 0) - .andWhere('is_disabled', 0) - .allowGraph('[permissions]') - .withGraphFetched('[permissions]') - .first() - .then((user) => { - if (user) { - // make sure user has all scopes of the token - // The `user` role is not added against the user row, so we have to just add it here to get past this check. - user.roles.push('user'); - - let is_ok = true; - _.forEach(token_data.scope, (scope_item) => { - if (_.indexOf(user.roles, scope_item) === -1) { - is_ok = false; - } - }); - - if (!is_ok) { - throw new error.AuthError('Invalid token scope for User'); - } else { - initialized = true; - user_roles = user.roles; - permissions = user.permissions; - } - } else { - throw new error.AuthError('User cannot be loaded for Token'); - } - }); - } else { - initialized = true; - } - }), - ); - } - }); - }; - - /** - * Fetches the object ids from the database, only once per object type, for this token. - * This only applies to USER token scopes, as all other tokens are not really bound - * by object scopes - * - * @param {String} object_type - * @returns {Promise} - */ - this.loadObjects = (object_type) => { - return new Promise((resolve, reject) => { - if (Token.hasScope('user')) { - if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) { - reject(new error.AuthError('User Token supplied without a User ID')); - } else { - const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; - let query; - - if (typeof object_cache[object_type] === 'undefined') { - switch (object_type) { - // USERS - should only return yourself - case 'users': - resolve(token_user_id ? [token_user_id] : []); - break; - - // Proxy Hosts - case 'proxy_hosts': - query = proxyHostModel.query().select('id').andWhere('is_deleted', 0); - - if (permissions.visibility === 'user') { - query.andWhere('owner_user_id', token_user_id); - } - - resolve( - query.then((rows) => { - const result = []; - _.forEach(rows, (rule_row) => { - result.push(rule_row.id); - }); - - // enum should not have less than 1 item - if (!result.length) { - result.push(0); - } - - return result; - }), - ); - break; - - // DEFAULT: null - default: - resolve(null); - break; - } - } else { - resolve(object_cache[object_type]); - } - } - } else { - resolve(null); - } - }).then((objects) => { - object_cache[object_type] = objects; - return objects; - }); - }; - - /** - * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema - * - * @param {String} permission_label - * @returns {Object} - */ - this.getObjectSchema = (permission_label) => { - const base_object_type = permission_label.split(':').shift(); - - const schema = { - $id: 'objects', - $schema: 'http://json-schema.org/draft-07/schema#', - description: 'Actor Properties', - type: 'object', - additionalProperties: false, - properties: { - user_id: { - anyOf: [ - { - type: 'number', - enum: [Token.get('attrs').id], - }, - ], - }, - scope: { - type: 'string', - pattern: '^' + Token.get('scope') + '$', - }, - }, - }; - - return this.loadObjects(base_object_type).then((object_result) => { - if (typeof object_result === 'object' && object_result !== null) { - schema.properties[base_object_type] = { - type: 'number', - enum: object_result, - minimum: 1, - }; - } else { - schema.properties[base_object_type] = { - type: 'number', - minimum: 1, - }; - } - - return schema; - }); - }; - - return { - token: Token, - - /** - * - * @param {Boolean} [allow_internal] - * @returns {Promise} - */ - load: (allow_internal) => { - return new Promise(function (resolve /*, reject */) { - if (token_string) { - resolve(Token.load(token_string)); - } else { - allow_internal_access = allow_internal; - resolve(allow_internal_access || null); - } - }); - }, - - reloadObjects: this.loadObjects, - - /** - * - * @param {String} permission - * @param {*} [data] - * @returns {Promise} - */ - can: (permission, data) => { - if (allow_internal_access === true) { - return Promise.resolve(true); - // return true; - } else { - return this.init() - .then(() => { - // initialized, token decoded ok - return this.getObjectSchema(permission).then((objectSchema) => { - const data_schema = { - [permission]: { - data, - scope: Token.get('scope'), - roles: user_roles, - permission_visibility: permissions.visibility, - permission_proxy_hosts: permissions.proxy_hosts, - permission_redirection_hosts: permissions.redirection_hosts, - permission_dead_hosts: permissions.dead_hosts, - permission_streams: permissions.streams, - permission_access_lists: permissions.access_lists, - permission_certificates: permissions.certificates, - }, - }; - - const permissionSchema = { - $schema: 'http://json-schema.org/draft-07/schema#', - $async: true, - $id: 'permissions', - additionalProperties: false, - properties: {}, - }; - - permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); - - // logger.info('objectSchema', JSON.stringify(objectSchema, null, 2)); - // logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2)); - // logger.info('data_schema', JSON.stringify(data_schema, null, 2)); - - const ajv = validator({ - verbose: true, - allErrors: true, - format: 'full', - missingRefs: 'fail', - breakOnError: true, - coerceTypes: true, - schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], - }); - - return ajv.validate('permissions', data_schema).then(() => { - return data_schema[permission]; - }); - }); - }) - .catch((err) => { - err.permission = permission; - err.permission_data = data; - logger.error(permission, data, err.message); - - throw new error.PermissionError('Permission Denied', err); - }); - } - }, - }; -}; diff --git a/backend/lib/access/access_lists-create.json b/backend/lib/access/access_lists-create.json deleted file mode 100644 index 5a16a8642..000000000 --- a/backend/lib/access/access_lists-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_access_lists", "roles"], - "properties": { - "permission_access_lists": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/access_lists-delete.json b/backend/lib/access/access_lists-delete.json deleted file mode 100644 index 5a16a8642..000000000 --- a/backend/lib/access/access_lists-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_access_lists", "roles"], - "properties": { - "permission_access_lists": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/access_lists-get.json b/backend/lib/access/access_lists-get.json deleted file mode 100644 index 8f6dd8cc6..000000000 --- a/backend/lib/access/access_lists-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_access_lists", "roles"], - "properties": { - "permission_access_lists": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/access_lists-list.json b/backend/lib/access/access_lists-list.json deleted file mode 100644 index 8f6dd8cc6..000000000 --- a/backend/lib/access/access_lists-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_access_lists", "roles"], - "properties": { - "permission_access_lists": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/access_lists-update.json b/backend/lib/access/access_lists-update.json deleted file mode 100644 index 5a16a8642..000000000 --- a/backend/lib/access/access_lists-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_access_lists", "roles"], - "properties": { - "permission_access_lists": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/auditlog-list.json b/backend/lib/access/auditlog-list.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/auditlog-list.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/certificates-create.json b/backend/lib/access/certificates-create.json deleted file mode 100644 index bcdf66742..000000000 --- a/backend/lib/access/certificates-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_certificates", "roles"], - "properties": { - "permission_certificates": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/certificates-delete.json b/backend/lib/access/certificates-delete.json deleted file mode 100644 index bcdf66742..000000000 --- a/backend/lib/access/certificates-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_certificates", "roles"], - "properties": { - "permission_certificates": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/certificates-get.json b/backend/lib/access/certificates-get.json deleted file mode 100644 index 9ccfa4f15..000000000 --- a/backend/lib/access/certificates-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_certificates", "roles"], - "properties": { - "permission_certificates": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/certificates-list.json b/backend/lib/access/certificates-list.json deleted file mode 100644 index 9ccfa4f15..000000000 --- a/backend/lib/access/certificates-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_certificates", "roles"], - "properties": { - "permission_certificates": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/certificates-update.json b/backend/lib/access/certificates-update.json deleted file mode 100644 index bcdf66742..000000000 --- a/backend/lib/access/certificates-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_certificates", "roles"], - "properties": { - "permission_certificates": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/dead_hosts-create.json b/backend/lib/access/dead_hosts-create.json deleted file mode 100644 index a276c681d..000000000 --- a/backend/lib/access/dead_hosts-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_dead_hosts", "roles"], - "properties": { - "permission_dead_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/dead_hosts-delete.json b/backend/lib/access/dead_hosts-delete.json deleted file mode 100644 index a276c681d..000000000 --- a/backend/lib/access/dead_hosts-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_dead_hosts", "roles"], - "properties": { - "permission_dead_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/dead_hosts-get.json b/backend/lib/access/dead_hosts-get.json deleted file mode 100644 index 87aa12e7d..000000000 --- a/backend/lib/access/dead_hosts-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_dead_hosts", "roles"], - "properties": { - "permission_dead_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/dead_hosts-list.json b/backend/lib/access/dead_hosts-list.json deleted file mode 100644 index 87aa12e7d..000000000 --- a/backend/lib/access/dead_hosts-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_dead_hosts", "roles"], - "properties": { - "permission_dead_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/dead_hosts-update.json b/backend/lib/access/dead_hosts-update.json deleted file mode 100644 index a276c681d..000000000 --- a/backend/lib/access/dead_hosts-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_dead_hosts", "roles"], - "properties": { - "permission_dead_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/permissions.json b/backend/lib/access/permissions.json deleted file mode 100644 index 8480f9a1c..000000000 --- a/backend/lib/access/permissions.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "perms", - "definitions": { - "view": { - "type": "string", - "pattern": "^(view|manage)$" - }, - "manage": { - "type": "string", - "pattern": "^(manage)$" - } - } -} diff --git a/backend/lib/access/proxy_hosts-create.json b/backend/lib/access/proxy_hosts-create.json deleted file mode 100644 index 166527a39..000000000 --- a/backend/lib/access/proxy_hosts-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_proxy_hosts", "roles"], - "properties": { - "permission_proxy_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/proxy_hosts-delete.json b/backend/lib/access/proxy_hosts-delete.json deleted file mode 100644 index 166527a39..000000000 --- a/backend/lib/access/proxy_hosts-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_proxy_hosts", "roles"], - "properties": { - "permission_proxy_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/proxy_hosts-get.json b/backend/lib/access/proxy_hosts-get.json deleted file mode 100644 index d88e4cfff..000000000 --- a/backend/lib/access/proxy_hosts-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_proxy_hosts", "roles"], - "properties": { - "permission_proxy_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/proxy_hosts-list.json b/backend/lib/access/proxy_hosts-list.json deleted file mode 100644 index d88e4cfff..000000000 --- a/backend/lib/access/proxy_hosts-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_proxy_hosts", "roles"], - "properties": { - "permission_proxy_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/proxy_hosts-update.json b/backend/lib/access/proxy_hosts-update.json deleted file mode 100644 index 166527a39..000000000 --- a/backend/lib/access/proxy_hosts-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_proxy_hosts", "roles"], - "properties": { - "permission_proxy_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/redirection_hosts-create.json b/backend/lib/access/redirection_hosts-create.json deleted file mode 100644 index 342babc88..000000000 --- a/backend/lib/access/redirection_hosts-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_redirection_hosts", "roles"], - "properties": { - "permission_redirection_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/redirection_hosts-delete.json b/backend/lib/access/redirection_hosts-delete.json deleted file mode 100644 index 342babc88..000000000 --- a/backend/lib/access/redirection_hosts-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_redirection_hosts", "roles"], - "properties": { - "permission_redirection_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/redirection_hosts-get.json b/backend/lib/access/redirection_hosts-get.json deleted file mode 100644 index ba2292064..000000000 --- a/backend/lib/access/redirection_hosts-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_redirection_hosts", "roles"], - "properties": { - "permission_redirection_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/redirection_hosts-list.json b/backend/lib/access/redirection_hosts-list.json deleted file mode 100644 index ba2292064..000000000 --- a/backend/lib/access/redirection_hosts-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_redirection_hosts", "roles"], - "properties": { - "permission_redirection_hosts": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/redirection_hosts-update.json b/backend/lib/access/redirection_hosts-update.json deleted file mode 100644 index 342babc88..000000000 --- a/backend/lib/access/redirection_hosts-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_redirection_hosts", "roles"], - "properties": { - "permission_redirection_hosts": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/reports-hosts.json b/backend/lib/access/reports-hosts.json deleted file mode 100644 index dbc9e0c0f..000000000 --- a/backend/lib/access/reports-hosts.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/user" - } - ] -} diff --git a/backend/lib/access/roles.json b/backend/lib/access/roles.json deleted file mode 100644 index 16b33b55b..000000000 --- a/backend/lib/access/roles.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "roles", - "definitions": { - "admin": { - "type": "object", - "required": ["scope", "roles"], - "properties": { - "scope": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^user$" - } - }, - "roles": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^admin$" - } - } - } - }, - "user": { - "type": "object", - "required": ["scope"], - "properties": { - "scope": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^user$" - } - } - } - } - } -} diff --git a/backend/lib/access/settings-get.json b/backend/lib/access/settings-get.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/settings-get.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/settings-list.json b/backend/lib/access/settings-list.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/settings-list.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/settings-update.json b/backend/lib/access/settings-update.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/settings-update.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/streams-create.json b/backend/lib/access/streams-create.json deleted file mode 100644 index fbeb1cc91..000000000 --- a/backend/lib/access/streams-create.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_streams", "roles"], - "properties": { - "permission_streams": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/streams-delete.json b/backend/lib/access/streams-delete.json deleted file mode 100644 index fbeb1cc91..000000000 --- a/backend/lib/access/streams-delete.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_streams", "roles"], - "properties": { - "permission_streams": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/streams-get.json b/backend/lib/access/streams-get.json deleted file mode 100644 index 7e9962874..000000000 --- a/backend/lib/access/streams-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_streams", "roles"], - "properties": { - "permission_streams": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/streams-list.json b/backend/lib/access/streams-list.json deleted file mode 100644 index 7e9962874..000000000 --- a/backend/lib/access/streams-list.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_streams", "roles"], - "properties": { - "permission_streams": { - "$ref": "perms#/definitions/view" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/streams-update.json b/backend/lib/access/streams-update.json deleted file mode 100644 index fbeb1cc91..000000000 --- a/backend/lib/access/streams-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["permission_streams", "roles"], - "properties": { - "permission_streams": { - "$ref": "perms#/definitions/manage" - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "enum": ["user"] - } - } - } - } - ] -} diff --git a/backend/lib/access/users-create.json b/backend/lib/access/users-create.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/users-create.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/users-delete.json b/backend/lib/access/users-delete.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/users-delete.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/users-get.json b/backend/lib/access/users-get.json deleted file mode 100644 index 2a2f0423a..000000000 --- a/backend/lib/access/users-get.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["data", "scope"], - "properties": { - "data": { - "$ref": "objects#/properties/users" - }, - "scope": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^user$" - } - } - } - } - ] -} diff --git a/backend/lib/access/users-list.json b/backend/lib/access/users-list.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/users-list.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/users-loginas.json b/backend/lib/access/users-loginas.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/users-loginas.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/users-password.json b/backend/lib/access/users-password.json deleted file mode 100644 index 2a2f0423a..000000000 --- a/backend/lib/access/users-password.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["data", "scope"], - "properties": { - "data": { - "$ref": "objects#/properties/users" - }, - "scope": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^user$" - } - } - } - } - ] -} diff --git a/backend/lib/access/users-permissions.json b/backend/lib/access/users-permissions.json deleted file mode 100644 index aeadc94ba..000000000 --- a/backend/lib/access/users-permissions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - } - ] -} diff --git a/backend/lib/access/users-update.json b/backend/lib/access/users-update.json deleted file mode 100644 index 2a2f0423a..000000000 --- a/backend/lib/access/users-update.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "anyOf": [ - { - "$ref": "roles#/definitions/admin" - }, - { - "type": "object", - "required": ["data", "scope"], - "properties": { - "data": { - "$ref": "objects#/properties/users" - }, - "scope": { - "type": "array", - "contains": { - "type": "string", - "pattern": "^user$" - } - } - } - } - ] -} diff --git a/backend/lib/certbot.js b/backend/lib/certbot.js deleted file mode 100644 index 8ae3dd9c9..000000000 --- a/backend/lib/certbot.js +++ /dev/null @@ -1,75 +0,0 @@ -const dnsPlugins = require('../certbot-dns-plugins.json'); -const utils = require('./utils'); -const error = require('./error'); -const logger = require('../logger').certbot; -const batchflow = require('batchflow'); - -const certbot = { - /** - * @param {array} pluginKeys - */ - installPlugins: async function (pluginKeys) { - let hasErrors = false; - - return new Promise((resolve, reject) => { - if (pluginKeys.length === 0) { - resolve(); - return; - } - - batchflow(pluginKeys) - .sequential() - .each((i, pluginKey, next) => { - certbot - .installPlugin(pluginKey) - .then(() => { - next(); - }) - .catch((err) => { - hasErrors = true; - next(err); - }); - }) - .error((err) => { - logger.error(err.message); - }) - .end(() => { - if (hasErrors) { - reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1)); - } else { - resolve(); - } - }); - }); - }, - - /** - * Installs a cerbot plugin given the key for the object from - * ../global/certbot-dns-plugins.json - * - * @param {string} pluginKey - * @returns {Object} - */ - installPlugin: async function (pluginKey) { - if (typeof dnsPlugins[pluginKey] === 'undefined') { - // throw Error(`Certbot plugin ${pluginKey} not found`); - throw new error.ItemNotFoundError(pluginKey); - } - - const plugin = dnsPlugins[pluginKey]; - logger.start(`Installing ${pluginKey}...`); - - const cmd = 'pip install --no-cache-dir ' + plugin.package_name; - return utils - .exec(cmd) - .then((result) => { - logger.complete(`Installed ${pluginKey}`); - return result; - }) - .catch((err) => { - throw err; - }); - }, -}; - -module.exports = certbot; diff --git a/backend/lib/config.js b/backend/lib/config.js deleted file mode 100644 index e7c1b9c1d..000000000 --- a/backend/lib/config.js +++ /dev/null @@ -1,186 +0,0 @@ -const fs = require('fs'); -const NodeRSA = require('node-rsa'); -const logger = require('../logger').global; - -const keysFile = '/data/etc/npm/keys.json'; - -let instance = null; - -// 1. Load from config file first (not recommended anymore) -// 2. Use config env variables next -const configure = () => { - const filename = (process.env.NODE_CONFIG_DIR || '/data/etc/npm') + '/' + (process.env.NODE_ENV || 'default') + '.json'; - if (fs.existsSync(filename)) { - let configData; - try { - configData = require(filename); - } catch { - // do nothing - } - - if (configData && configData.database) { - logger.info(`Using configuration from file: ${filename}`); - instance = configData; - instance.keys = getKeys(); - return; - } - } - - const envMysqlHost = process.env.DB_MYSQL_HOST || null; - const envMysqlUser = process.env.DB_MYSQL_USER || null; - const envMysqlName = process.env.DB_MYSQL_NAME || null; - const envMysqlTls = process.env.DB_MYSQL_TLS || null; - const envMysqlCa = process.env.DB_MYSQL_CA || '/etc/ssl/certs/ca-certificates.crt'; - if (envMysqlHost && envMysqlUser && envMysqlName) { - // we have enough mysql creds to go with mysql - logger.info('Using MySQL configuration'); - instance = { - database: { - engine: 'mysql', - host: envMysqlHost, - port: process.env.DB_MYSQL_PORT || 3306, - user: envMysqlUser, - password: process.env.DB_MYSQL_PASSWORD, - name: envMysqlName, - ssl: envMysqlTls ? { ca: fs.readFileSync(envMysqlCa) } : false, - }, - keys: getKeys(), - }; - return; - } - - const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/etc/npm/database.sqlite'; - logger.info(`Using Sqlite: ${envSqliteFile}`); - instance = { - database: { - engine: 'knex-native', - knex: { - client: 'sqlite3', - connection: { - filename: envSqliteFile, - }, - useNullAsDefault: true, - }, - }, - keys: getKeys(), - }; -}; - -const getKeys = () => { - // Get keys from file - if (!fs.existsSync(keysFile)) { - generateKeys(); - } else if (process.env.DEBUG) { - logger.info('Keys file exists OK'); - } - try { - return require(keysFile); - } catch (err) { - logger.error('Could not read JWT key pair from config file: ' + keysFile, err); - process.exit(1); - } -}; - -const generateKeys = () => { - logger.info('Creating a new JWT key pair...'); - // Now create the keys and save them in the config. - const key = new NodeRSA({ b: 2048 }); - key.generateKeyPair(); - - const keys = { - key: key.exportKey('private').toString(), - pub: key.exportKey('public').toString(), - }; - - // Write keys config - try { - fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); - } catch (err) { - logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message); - process.exit(1); - } - logger.info('Wrote JWT key pair to config file: ' + keysFile); -}; - -module.exports = { - /** - * - * @param {string} key ie: 'database' or 'database.engine' - * @returns {boolean} - */ - has: function (key) { - instance === null && configure(); - const keys = key.split('.'); - let level = instance; - let has = true; - keys.forEach((keyItem) => { - if (typeof level[keyItem] === 'undefined') { - has = false; - } else { - level = level[keyItem]; - } - }); - - return has; - }, - - /** - * Gets a specific key from the top level - * - * @param {string} key - * @returns {*} - */ - get: function (key) { - instance === null && configure(); - if (key && typeof instance[key] !== 'undefined') { - return instance[key]; - } - return instance; - }, - - /** - * Is this a sqlite configuration? - * - * @returns {boolean} - */ - isSqlite: function () { - instance === null && configure(); - return instance.database.knex && instance.database.knex.client === 'sqlite3'; - }, - - /** - * Are we running in debug mode? - * - * @returns {boolean} - */ - debug: function () { - return !!process.env.DEBUG; - }, - - /** - * Returns a public key - * - * @returns {string} - */ - getPublicKey: function () { - instance === null && configure(); - return instance.keys.pub; - }, - - /** - * Returns a private key - * - * @returns {string} - */ - getPrivateKey: function () { - instance === null && configure(); - return instance.keys.key; - }, - - /** - * @returns {boolean} - */ - useLetsencryptStaging: function () { - return !!process.env.LE_STAGING; - }, -}; diff --git a/backend/lib/error.js b/backend/lib/error.js deleted file mode 100644 index b78c46302..000000000 --- a/backend/lib/error.js +++ /dev/null @@ -1,98 +0,0 @@ -const _ = require('lodash'); -const util = require('util'); - -module.exports = { - PermissionError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = 'Permission Denied'; - this.public = true; - this.status = 403; - }, - - ItemNotFoundError: function (id, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = 'Item Not Found - ' + id; - this.public = true; - this.status = 404; - }, - - AuthError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.public = true; - this.status = 401; - }, - - InternalError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.status = 500; - this.public = false; - }, - - InternalValidationError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.status = 400; - this.public = false; - }, - - ConfigurationError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.status = 400; - this.public = true; - }, - - CacheError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; - this.previous = previous; - this.status = 500; - this.public = false; - }, - - ValidationError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.public = true; - this.status = 400; - }, - - AssertionFailedError: function (message, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = message; - this.public = false; - this.status = 400; - }, - - CommandError: function (stdErr, code, previous) { - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.previous = previous; - this.message = stdErr; - this.code = code; - this.public = false; - }, -}; - -_.forEach(module.exports, function (error) { - util.inherits(error, Error); -}); diff --git a/backend/lib/express/cors.js b/backend/lib/express/cors.js deleted file mode 100644 index 8a5297847..000000000 --- a/backend/lib/express/cors.js +++ /dev/null @@ -1,36 +0,0 @@ -const validator = require('../validator'); - -module.exports = function (req, res, next) { - if (req.headers.origin) { - const originSchema = { - oneOf: [ - { - type: 'string', - pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$', - }, - { - type: 'string', - pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}\\:?)+\\])?/?(:[0-9]+)?$', - }, - ], - }; - - // very relaxed validation.... - validator(originSchema, req.headers.origin) - .then(function () { - res.set({ - 'Access-Control-Allow-Origin': req.headers.origin, - 'Access-Control-Allow-Credentials': true, - 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', - 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', - 'Access-Control-Max-Age': 5 * 60, - 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', - }); - next(); - }) - .catch(next); - } else { - // No origin - next(); - } -}; diff --git a/backend/lib/express/jwt-decode.js b/backend/lib/express/jwt-decode.js deleted file mode 100644 index 38563d00e..000000000 --- a/backend/lib/express/jwt-decode.js +++ /dev/null @@ -1,15 +0,0 @@ -const Access = require('../access'); - -module.exports = () => { - return function (req, res, next) { - res.locals.access = null; - const access = new Access(res.locals.token || null); - access - .load() - .then(() => { - res.locals.access = access; - next(); - }) - .catch(next); - }; -}; diff --git a/backend/lib/express/jwt.js b/backend/lib/express/jwt.js deleted file mode 100644 index adaabafa4..000000000 --- a/backend/lib/express/jwt.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = function () { - return function (req, res, next) { - if (req.headers.authorization) { - const parts = req.headers.authorization.split(' '); - - if (parts && parts[0] === 'Bearer' && parts[1]) { - res.locals.token = parts[1]; - } - } - - next(); - }; -}; diff --git a/backend/lib/express/pagination.js b/backend/lib/express/pagination.js deleted file mode 100644 index ac01a66a1..000000000 --- a/backend/lib/express/pagination.js +++ /dev/null @@ -1,53 +0,0 @@ -const _ = require('lodash'); - -module.exports = function (default_sort, default_offset, default_limit, max_limit) { - /** - * This will setup the req query params with filtered data and defaults - * - * sort will be an array of fields and their direction - * offset will be an int, defaulting to zero if no other default supplied - * limit will be an int, defaulting to 50 if no other default supplied, and limited to the max if that was supplied - * - */ - - return function (req, res, next) { - req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10); - req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10); - - if (max_limit && req.query.limit > max_limit) { - req.query.limit = max_limit; - } - - // Sorting - let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort; - const myRegexp = /.*\.(asc|desc)$/gi; - const sort_array = []; - - sort = sort.split(','); - _.map(sort, function (val) { - const matches = myRegexp.exec(val); - - if (matches !== null) { - const dir = matches[1]; - sort_array.push({ - field: val.substr(0, val.length - (dir.length + 1)), - dir: dir.toLowerCase(), - }); - } else { - sort_array.push({ - field: val, - dir: 'asc', - }); - } - }); - - // Sort will now be in this format: - // [ - // { field: 'field1', dir: 'asc' }, - // { field: 'field2', dir: 'desc' } - // ] - - req.query.sort = sort_array; - next(); - }; -}; diff --git a/backend/lib/express/user-id-from-me.js b/backend/lib/express/user-id-from-me.js deleted file mode 100644 index 4a37a4069..000000000 --- a/backend/lib/express/user-id-from-me.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = (req, res, next) => { - if (req.params.user_id === 'me' && res.locals.access) { - req.params.user_id = res.locals.access.token.get('attrs').id; - } else { - req.params.user_id = parseInt(req.params.user_id, 10); - } - - next(); -}; diff --git a/backend/lib/helpers.js b/backend/lib/helpers.js deleted file mode 100644 index bb09abbaf..000000000 --- a/backend/lib/helpers.js +++ /dev/null @@ -1,30 +0,0 @@ -const moment = require('moment'); - -module.exports = { - /** - * Takes an expression such as 30d and returns a moment object of that date in future - * - * Key Shorthand - * ================== - * years y - * quarters Q - * months M - * weeks w - * days d - * hours h - * minutes m - * seconds s - * milliseconds ms - * - * @param {String} expression - * @returns {Object} - */ - parseDatePeriod: function (expression) { - const matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); - if (matches) { - return moment().add(matches[1], matches[2]); - } - - return null; - }, -}; diff --git a/backend/lib/migrate_template.js b/backend/lib/migrate_template.js deleted file mode 100644 index 21da446fe..000000000 --- a/backend/lib/migrate_template.js +++ /dev/null @@ -1,54 +0,0 @@ -const migrate_name = 'identifier_for_migrate'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex, Promise) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - // Create Table example: - - /* return knex.schema.createTable('notification', (table) => { - table.increments().primary(); - table.string('name').notNull(); - table.string('type').notNull(); - table.integer('created_on').notNull(); - table.integer('modified_on').notNull(); - }) - .then(function () { - logger.info('[' + migrate_name + '] Notification Table created'); - }); */ - - logger.info('[' + migrate_name + '] Migrating Up Complete'); - - return Promise.resolve(true); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - // Drop table example: - - /* return knex.schema.dropTable('notification') - .then(() => { - logger.info('[' + migrate_name + '] Notification Table dropped'); - }); */ - - logger.info('[' + migrate_name + '] Migrating Down Complete'); - - return Promise.resolve(true); -}; diff --git a/backend/lib/utils.js b/backend/lib/utils.js deleted file mode 100644 index 9b3983254..000000000 --- a/backend/lib/utils.js +++ /dev/null @@ -1,138 +0,0 @@ -const _ = require('lodash'); -const exec = require('child_process').exec; -const spawn = require('child_process').spawn; -const execFile = require('child_process').execFile; -const { Liquid } = require('liquidjs'); -const error = require('./error'); -// const logger = require('../logger').global; - -module.exports = { - /** - * @param {String} cmd - */ - exec: async function (cmd, options = {}) { - // logger.debug('CMD:', cmd); - - const { stdout, stderr } = await new Promise((resolve, reject) => { - const child = exec(cmd, options, (isError, stdout, stderr) => { - if (isError) { - reject(new error.CommandError(stderr, isError)); - } else { - resolve({ stdout, stderr }); - } - }); - - child.on('error', (e) => { - reject(new error.CommandError(stderr, 1, e)); - }); - }); - return stdout; - }, - - /** - * @param {String} cmd - * @param {Array} args - */ - execFile: async function (cmd, args, options = {}) { - // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); - - const { stdout, stderr } = await new Promise((resolve, reject) => { - const child = execFile(cmd, args, options, (isError, stdout, stderr) => { - if (isError) { - reject(new error.CommandError(stderr, isError)); - } else { - resolve({ stdout, stderr }); - } - }); - - child.on('error', (e) => { - reject(new error.CommandError(stderr, 1, e)); - }); - }); - return stdout; - }, - - /** - * @param {String} cmd - */ - execfg: function (cmd) { - return new Promise((resolve, reject) => { - const childProcess = spawn(cmd, { - shell: true, - detached: true, - stdio: 'inherit', - }); - - childProcess.on('error', (err) => { - reject(err); - }); - - childProcess.on('close', (code) => { - if (code !== 0) { - reject(new Error(`Command '${cmd}' exited with code ${code}`)); - } else { - resolve(); - } - }); - }); - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRow: function (omissions) { - /** - * @param {Object} row - * @returns {Object} - */ - return (row) => { - return _.omit(row, omissions); - }; - }, - - /** - * Used in objection query builder - * - * @param {Array} omissions - * @returns {Function} - */ - omitRows: function (omissions) { - /** - * @param {Array} rows - * @returns {Object} - */ - return (rows) => { - rows.forEach((row, idx) => { - rows[idx] = _.omit(row, omissions); - }); - return rows; - }; - }, - - /** - * @returns {Object} Liquid render engine - */ - getRenderEngine: function () { - const renderEngine = new Liquid({ - root: __dirname + '/../templates/', - }); - - /** - * nginxAccessRule expects the object given to have 2 properties: - * - * directive string - * address string - */ - renderEngine.registerFilter('nginxAccessRule', (v) => { - if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) { - return `${v.directive} ${v.address};`; - } - return ''; - }); - - return renderEngine; - }, -}; diff --git a/backend/lib/validator/api.js b/backend/lib/validator/api.js deleted file mode 100644 index 9b577cdeb..000000000 --- a/backend/lib/validator/api.js +++ /dev/null @@ -1,43 +0,0 @@ -const error = require('../error'); -const path = require('path'); -const parser = require('@apidevtools/json-schema-ref-parser'); - -const ajv = require('ajv')({ - verbose: true, - validateSchema: true, - allErrors: false, - format: 'full', - coerceTypes: true, -}); - -/** - * @param {Object} schema - * @param {Object} payload - * @returns {Promise} - */ -function apiValidator(schema, payload /*, description */) { - return new Promise(function Promise_apiValidator(resolve, reject) { - if (typeof payload === 'undefined') { - reject(new error.ValidationError('Payload is undefined')); - } - - const validate = ajv.compile(schema); - const valid = validate(payload); - - if (valid && !validate.errors) { - resolve(payload); - } else { - const message = ajv.errorsText(validate.errors); - const err = new error.ValidationError(message); - err.debug = [validate.errors, payload]; - reject(err); - } - }); -} - -apiValidator.loadSchemas = parser.dereference(path.resolve('schema/index.json')).then((schema) => { - ajv.addSchema(schema); - return schema; -}); - -module.exports = apiValidator; diff --git a/backend/lib/validator/index.js b/backend/lib/validator/index.js deleted file mode 100644 index 419a9cf47..000000000 --- a/backend/lib/validator/index.js +++ /dev/null @@ -1,43 +0,0 @@ -const _ = require('lodash'); -const error = require('../error'); -const definitions = require('../../schema/definitions.json'); - -RegExp.prototype.toJSON = RegExp.prototype.toString; - -const ajv = require('ajv')({ - verbose: true, - allErrors: true, - format: 'full', // strict regexes for format checks - coerceTypes: true, - schemas: [definitions], -}); - -/** - * - * @param {Object} schema - * @param {Object} payload - * @returns {Promise} - */ -function validator(schema, payload) { - return new Promise(function (resolve, reject) { - if (!payload) { - reject(new error.InternalValidationError('Payload is falsy')); - } else { - try { - const validate = ajv.compile(schema); - - const valid = validate(payload); - if (valid && !validate.errors) { - resolve(_.cloneDeep(payload)); - } else { - const message = ajv.errorsText(validate.errors); - reject(new error.InternalValidationError(message)); - } - } catch (err) { - reject(err); - } - } - }); -} - -module.exports = validator; diff --git a/backend/logger.js b/backend/logger.js deleted file mode 100644 index 64c451c84..000000000 --- a/backend/logger.js +++ /dev/null @@ -1,14 +0,0 @@ -const { Signale } = require('signale'); - -module.exports = { - global: new Signale({ scope: 'Global ' }), - migrate: new Signale({ scope: 'Migrate ' }), - express: new Signale({ scope: 'Express ' }), - access: new Signale({ scope: 'Access ' }), - nginx: new Signale({ scope: 'Nginx ' }), - ssl: new Signale({ scope: 'SSL ' }), - certbot: new Signale({ scope: 'Certbot ' }), - import: new Signale({ scope: 'Importer ' }), - setup: new Signale({ scope: 'Setup ' }), - ip_ranges: new Signale({ scope: 'IP Ranges' }), -}; diff --git a/backend/migrate.js b/backend/migrate.js deleted file mode 100644 index 773109ab4..000000000 --- a/backend/migrate.js +++ /dev/null @@ -1,14 +0,0 @@ -const db = require('./db'); -const logger = require('./logger').migrate; - -module.exports = { - latest: function () { - return db.migrate.currentVersion().then((version) => { - logger.info('Current database version:', version); - return db.migrate.latest({ - tableName: 'migrations', - directory: 'migrations', - }); - }); - }, -}; diff --git a/backend/migrations/20180618015850_initial.js b/backend/migrations/20180618015850_initial.js deleted file mode 100644 index 6377c163e..000000000 --- a/backend/migrations/20180618015850_initial.js +++ /dev/null @@ -1,205 +0,0 @@ -const migrate_name = 'initial-schema'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .createTable('auth', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('type', 30).notNull(); - table.string('secret').notNull(); - table.json('meta').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - }) - .then(() => { - logger.info('[' + migrate_name + '] auth Table created'); - - return knex.schema.createTable('user', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('is_disabled').notNull().unsigned().defaultTo(0); - table.string('email').notNull(); - table.string('name').notNull(); - table.string('nickname').notNull(); - table.string('avatar').notNull(); - table.json('roles').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] user Table created'); - - return knex.schema.createTable('user_permission', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('visibility').notNull(); - table.string('proxy_hosts').notNull(); - table.string('redirection_hosts').notNull(); - table.string('dead_hosts').notNull(); - table.string('streams').notNull(); - table.string('access_lists').notNull(); - table.string('certificates').notNull(); - table.unique('user_id'); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] user_permission Table created'); - - return knex.schema.createTable('proxy_host', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_ip').notNull(); - table.integer('forward_port').notNull().unsigned(); - table.integer('access_list_id').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('caching_enabled').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table created'); - - return knex.schema.createTable('redirection_host', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.string('forward_domain_name').notNull(); - table.integer('preserve_path').notNull().unsigned().defaultTo(0); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.integer('block_exploits').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table created'); - - return knex.schema.createTable('dead_host', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.json('domain_names').notNull(); - table.integer('certificate_id').notNull().unsigned().defaultTo(0); - table.integer('ssl_forced').notNull().unsigned().defaultTo(0); - table.text('advanced_config').notNull().defaultTo(''); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] dead_host Table created'); - - return knex.schema.createTable('stream', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.integer('incoming_port').notNull().unsigned(); - table.string('forward_ip').notNull(); - table.integer('forwarding_port').notNull().unsigned(); - table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0); - table.integer('udp_forwarding').notNull().unsigned().defaultTo(0); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] stream Table created'); - - return knex.schema.createTable('access_list', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('name').notNull(); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list Table created'); - - return knex.schema.createTable('certificate', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('owner_user_id').notNull().unsigned(); - table.integer('is_deleted').notNull().unsigned().defaultTo(0); - table.string('provider').notNull(); - table.string('nice_name').notNull().defaultTo(''); - table.json('domain_names').notNull(); - table.dateTime('expires_on').notNull(); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] certificate Table created'); - - return knex.schema.createTable('access_list_auth', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('username').notNull(); - table.string('password').notNull(); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list_auth Table created'); - - return knex.schema.createTable('audit_log', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('user_id').notNull().unsigned(); - table.string('object_type').notNull().defaultTo(''); - table.integer('object_id').notNull().unsigned().defaultTo(0); - table.string('action').notNull(); - table.json('meta').notNull(); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] audit_log Table created'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down the initial data."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20180929054513_websockets.js b/backend/migrations/20180929054513_websockets.js deleted file mode 100644 index 51c2d3ee3..000000000 --- a/backend/migrations/20180929054513_websockets.js +++ /dev/null @@ -1,35 +0,0 @@ -const migrate_name = 'websockets'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20181019052346_forward_host.js b/backend/migrations/20181019052346_forward_host.js deleted file mode 100644 index 5a7c05746..000000000 --- a/backend/migrations/20181019052346_forward_host.js +++ /dev/null @@ -1,35 +0,0 @@ -const migrate_name = 'forward_host'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.renameColumn('forward_ip', 'forward_host'); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20181113041458_http2_support.js b/backend/migrations/20181113041458_http2_support.js deleted file mode 100644 index 0ec6f1243..000000000 --- a/backend/migrations/20181113041458_http2_support.js +++ /dev/null @@ -1,49 +0,0 @@ -const migrate_name = 'http2_support'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('http2_support').notNull().unsigned().defaultTo(0); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20181213013211_forward_scheme.js b/backend/migrations/20181213013211_forward_scheme.js deleted file mode 100644 index c08345453..000000000 --- a/backend/migrations/20181213013211_forward_scheme.js +++ /dev/null @@ -1,35 +0,0 @@ -const migrate_name = 'forward_scheme'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.string('forward_scheme').notNull().defaultTo('http'); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20190104035154_disabled.js b/backend/migrations/20190104035154_disabled.js deleted file mode 100644 index 6731ea9e5..000000000 --- a/backend/migrations/20190104035154_disabled.js +++ /dev/null @@ -1,56 +0,0 @@ -const migrate_name = 'disabled'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.integer('enabled').notNull().unsigned().defaultTo(1); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('enabled').notNull().unsigned().defaultTo(1); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('enabled').notNull().unsigned().defaultTo(1); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); - - return knex.schema.table('stream', function (stream) { - stream.integer('enabled').notNull().unsigned().defaultTo(1); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] stream Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20190215115310_customlocations.js b/backend/migrations/20190215115310_customlocations.js deleted file mode 100644 index a3f2c744d..000000000 --- a/backend/migrations/20190215115310_customlocations.js +++ /dev/null @@ -1,36 +0,0 @@ -const migrate_name = 'custom_locations'; -const logger = require('../logger').migrate; - -/** - * Migrate - * Extends proxy_host table with locations field - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.json('locations'); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20190218060101_hsts.js b/backend/migrations/20190218060101_hsts.js deleted file mode 100644 index 3f994e4c6..000000000 --- a/backend/migrations/20190218060101_hsts.js +++ /dev/null @@ -1,52 +0,0 @@ -const migrate_name = 'hsts'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('proxy_host', function (proxy_host) { - proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); - }) - .then(() => { - logger.info('[' + migrate_name + '] proxy_host Table altered'); - - return knex.schema.table('redirection_host', function (redirection_host) { - redirection_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - redirection_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - - return knex.schema.table('dead_host', function (dead_host) { - dead_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); - dead_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] dead_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20190227065017_settings.js b/backend/migrations/20190227065017_settings.js deleted file mode 100644 index 196f4c061..000000000 --- a/backend/migrations/20190227065017_settings.js +++ /dev/null @@ -1,39 +0,0 @@ -const migrate_name = 'settings'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .createTable('setting', (table) => { - table.string('id').notNull().primary(); - table.string('name', 100).notNull(); - table.string('description', 255).notNull(); - table.string('value', 255).notNull(); - table.json('meta').notNull(); - }) - .then(() => { - logger.info('[' + migrate_name + '] setting Table created'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down the initial data."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20200410143839_access_list_client.js b/backend/migrations/20200410143839_access_list_client.js deleted file mode 100644 index a045c26bd..000000000 --- a/backend/migrations/20200410143839_access_list_client.js +++ /dev/null @@ -1,51 +0,0 @@ -const migrate_name = 'access_list_client'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .createTable('access_list_client', (table) => { - table.increments().primary(); - table.dateTime('created_on').notNull(); - table.dateTime('modified_on').notNull(); - table.integer('access_list_id').notNull().unsigned(); - table.string('address').notNull(); - table.string('directive').notNull(); - table.json('meta').notNull(); - }) - .then(function () { - logger.info('[' + migrate_name + '] access_list_client Table created'); - - return knex.schema.table('access_list', function (access_list) { - access_list.integer('satify_any').notNull().defaultTo(0); - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema.dropTable('access_list_client').then(() => { - logger.info('[' + migrate_name + '] access_list_client Table dropped'); - }); -}; diff --git a/backend/migrations/20200410143840_access_list_client_fix.js b/backend/migrations/20200410143840_access_list_client_fix.js deleted file mode 100644 index ff26690d7..000000000 --- a/backend/migrations/20200410143840_access_list_client_fix.js +++ /dev/null @@ -1,35 +0,0 @@ -const migrate_name = 'access_list_client_fix'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('access_list', function (access_list) { - access_list.renameColumn('satify_any', 'satisfy_any'); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex, Promise) { - logger.warn('[' + migrate_name + "] You can't migrate down this one."); - return Promise.resolve(true); -}; diff --git a/backend/migrations/20201014143841_pass_auth.js b/backend/migrations/20201014143841_pass_auth.js deleted file mode 100644 index 8f222978b..000000000 --- a/backend/migrations/20201014143841_pass_auth.js +++ /dev/null @@ -1,42 +0,0 @@ -const migrate_name = 'pass_auth'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('access_list', function (access_list) { - access_list.integer('pass_auth').notNull().defaultTo(1); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema - .table('access_list', function (access_list) { - access_list.dropColumn('pass_auth'); - }) - .then(() => { - logger.info('[' + migrate_name + '] access_list pass_auth Column dropped'); - }); -}; diff --git a/backend/migrations/20210210154702_redirection_scheme.js b/backend/migrations/20210210154702_redirection_scheme.js deleted file mode 100644 index ef9e7e962..000000000 --- a/backend/migrations/20210210154702_redirection_scheme.js +++ /dev/null @@ -1,42 +0,0 @@ -const migrate_name = 'redirection_scheme'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('redirection_host', (table) => { - table.string('forward_scheme').notNull().defaultTo('$scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema - .table('redirection_host', (table) => { - table.dropColumn('forward_scheme'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - }); -}; diff --git a/backend/migrations/20210210154703_redirection_status_code.js b/backend/migrations/20210210154703_redirection_status_code.js deleted file mode 100644 index b16d7b348..000000000 --- a/backend/migrations/20210210154703_redirection_status_code.js +++ /dev/null @@ -1,42 +0,0 @@ -const migrate_name = 'redirection_status_code'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('redirection_host', (table) => { - table.integer('forward_http_code').notNull().unsigned().defaultTo(302); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema - .table('redirection_host', (table) => { - table.dropColumn('forward_http_code'); - }) - .then(function () { - logger.info('[' + migrate_name + '] redirection_host Table altered'); - }); -}; diff --git a/backend/migrations/20210423103500_stream_domain.js b/backend/migrations/20210423103500_stream_domain.js deleted file mode 100644 index 55f9c8da6..000000000 --- a/backend/migrations/20210423103500_stream_domain.js +++ /dev/null @@ -1,42 +0,0 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .table('stream', (table) => { - table.renameColumn('forward_ip', 'forwarding_host'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema - .table('stream', (table) => { - table.renameColumn('forwarding_host', 'forward_ip'); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); - }); -}; diff --git a/backend/migrations/20211108145214_regenerate_default_host.js b/backend/migrations/20211108145214_regenerate_default_host.js deleted file mode 100644 index 82e6c403e..000000000 --- a/backend/migrations/20211108145214_regenerate_default_host.js +++ /dev/null @@ -1,51 +0,0 @@ -const migrate_name = 'stream_domain'; -const logger = require('../logger').migrate; -const internalNginx = require('../internal/nginx'); - -async function regenerateDefaultHost(knex) { - const row = await knex('setting').select('*').where('id', 'default-site').first(); - - if (!row) { - return Promise.resolve(); - } - - return internalNginx - .deleteConfig('default') - .then(() => { - return internalNginx.generateConfig('default', row); - }) - .then(() => { - return internalNginx.test(); - }) - .then(() => { - return internalNginx.reload(); - }); -} - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return regenerateDefaultHost(knex); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return regenerateDefaultHost(knex); -}; diff --git a/backend/models/access_list.js b/backend/models/access_list.js deleted file mode 100644 index 3de0a96f0..000000000 --- a/backend/models/access_list.js +++ /dev/null @@ -1,86 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessListAuth = require('./access_list_auth'); -const AccessListClient = require('./access_list_client'); -const now = require('./now_helper'); - -Model.knex(db); - -class AccessList extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'AccessList'; - } - - static get tableName() { - return 'access_list'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - const ProxyHost = require('./proxy_host'); - - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'access_list.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - items: { - relation: Model.HasManyRelation, - modelClass: AccessListAuth, - join: { - from: 'access_list.id', - to: 'access_list_auth.access_list_id', - }, - }, - clients: { - relation: Model.HasManyRelation, - modelClass: AccessListClient, - join: { - from: 'access_list.id', - to: 'access_list_client.access_list_id', - }, - }, - proxy_hosts: { - relation: Model.HasManyRelation, - modelClass: ProxyHost, - join: { - from: 'access_list.id', - to: 'proxy_host.access_list_id', - }, - modify: function (qb) { - qb.where('proxy_host.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = AccessList; diff --git a/backend/models/access_list_auth.js b/backend/models/access_list_auth.js deleted file mode 100644 index 0c066f199..000000000 --- a/backend/models/access_list_auth.js +++ /dev/null @@ -1,54 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); - -Model.knex(db); - -class AccessListAuth extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'AccessListAuth'; - } - - static get tableName() { - return 'access_list_auth'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - return { - access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_auth.access_list_id', - to: 'access_list.id', - }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = AccessListAuth; diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js deleted file mode 100644 index 41ad6a998..000000000 --- a/backend/models/access_list_client.js +++ /dev/null @@ -1,54 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); - -Model.knex(db); - -class AccessListClient extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'AccessListClient'; - } - - static get tableName() { - return 'access_list_client'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - return { - access_list: { - relation: Model.HasOneRelation, - modelClass: require('./access_list'), - join: { - from: 'access_list_client.access_list_id', - to: 'access_list.id', - }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = AccessListClient; diff --git a/backend/models/audit-log.js b/backend/models/audit-log.js deleted file mode 100644 index ec482bd84..000000000 --- a/backend/models/audit-log.js +++ /dev/null @@ -1,52 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); - -Model.knex(db); - -class AuditLog extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'AuditLog'; - } - - static get tableName() { - return 'audit_log'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - return { - user: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'audit_log.user_id', - to: 'user.id', - }, - }, - }; - } -} - -module.exports = AuditLog; diff --git a/backend/models/auth.js b/backend/models/auth.js deleted file mode 100644 index 4c194f254..000000000 --- a/backend/models/auth.js +++ /dev/null @@ -1,82 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const bcrypt = require('bcrypt'); -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); - -Model.knex(db); - -function encryptPassword() { - /* jshint -W040 */ - const _this = this; - - if (_this.type === 'password' && _this.secret) { - return bcrypt.hash(_this.secret, 13).then(function (hash) { - _this.secret = hash; - }); - } - - return null; -} - -class Auth extends Model { - $beforeInsert(queryContext) { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - - return encryptPassword.apply(this, queryContext); - } - - $beforeUpdate(queryContext) { - this.modified_on = now(); - return encryptPassword.apply(this, queryContext); - } - - /** - * Verify a plain password against the encrypted password - * - * @param {String} password - * @returns {Promise} - */ - verifyPassword(password) { - return bcrypt.compare(password, this.secret); - } - - static get name() { - return 'Auth'; - } - - static get tableName() { - return 'auth'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - return { - user: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'auth.user_id', - to: 'user.id', - }, - filter: { - is_deleted: 0, - }, - }, - }; - } -} - -module.exports = Auth; diff --git a/backend/models/certificate.js b/backend/models/certificate.js deleted file mode 100644 index 6c112a183..000000000 --- a/backend/models/certificate.js +++ /dev/null @@ -1,65 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); - -Model.knex(db); - -class Certificate extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for expires_on - if (typeof this.expires_on === 'undefined') { - this.expires_on = now(); - } - - // Default for domain_names - if (typeof this.domain_names === 'undefined') { - this.domain_names = []; - } - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'Certificate'; - } - - static get tableName() { - return 'certificate'; - } - - static get jsonAttributes() { - return ['domain_names', 'meta']; - } - - static get relationMappings() { - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'certificate.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = Certificate; diff --git a/backend/models/dead_host.js b/backend/models/dead_host.js deleted file mode 100644 index 725ebfef3..000000000 --- a/backend/models/dead_host.js +++ /dev/null @@ -1,72 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); - -Model.knex(db); - -class DeadHost extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for domain_names - if (typeof this.domain_names === 'undefined') { - this.domain_names = []; - } - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'DeadHost'; - } - - static get tableName() { - return 'dead_host'; - } - - static get jsonAttributes() { - return ['domain_names', 'meta']; - } - - static get relationMappings() { - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'dead_host.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - certificate: { - relation: Model.HasOneRelation, - modelClass: Certificate, - join: { - from: 'dead_host.certificate_id', - to: 'certificate.id', - }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = DeadHost; diff --git a/backend/models/now_helper.js b/backend/models/now_helper.js deleted file mode 100644 index 99a0778be..000000000 --- a/backend/models/now_helper.js +++ /dev/null @@ -1,12 +0,0 @@ -const db = require('../db'); -const config = require('../lib/config'); -const Model = require('objection').Model; - -Model.knex(db); - -module.exports = function () { - if (config.isSqlite()) { - return Model.raw("datetime('now','localtime')"); - } - return Model.raw('NOW()'); -}; diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js deleted file mode 100644 index 5959f213a..000000000 --- a/backend/models/proxy_host.js +++ /dev/null @@ -1,84 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessList = require('./access_list'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); - -Model.knex(db); - -class ProxyHost extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for domain_names - if (typeof this.domain_names === 'undefined') { - this.domain_names = []; - } - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'ProxyHost'; - } - - static get tableName() { - return 'proxy_host'; - } - - static get jsonAttributes() { - return ['domain_names', 'meta', 'locations']; - } - - static get relationMappings() { - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'proxy_host.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - access_list: { - relation: Model.HasOneRelation, - modelClass: AccessList, - join: { - from: 'proxy_host.access_list_id', - to: 'access_list.id', - }, - modify: function (qb) { - qb.where('access_list.is_deleted', 0); - }, - }, - certificate: { - relation: Model.HasOneRelation, - modelClass: Certificate, - join: { - from: 'proxy_host.certificate_id', - to: 'certificate.id', - }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = ProxyHost; diff --git a/backend/models/redirection_host.js b/backend/models/redirection_host.js deleted file mode 100644 index 5c42acfb8..000000000 --- a/backend/models/redirection_host.js +++ /dev/null @@ -1,74 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const Certificate = require('./certificate'); -const now = require('./now_helper'); - -Model.knex(db); - -class RedirectionHost extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for domain_names - if (typeof this.domain_names === 'undefined') { - this.domain_names = []; - } - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - - this.domain_names.sort(); - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'RedirectionHost'; - } - - static get tableName() { - return 'redirection_host'; - } - - static get jsonAttributes() { - return ['domain_names', 'meta']; - } - - static get relationMappings() { - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'redirection_host.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - certificate: { - relation: Model.HasOneRelation, - modelClass: Certificate, - join: { - from: 'redirection_host.certificate_id', - to: 'certificate.id', - }, - modify: function (qb) { - qb.where('certificate.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = RedirectionHost; diff --git a/backend/models/setting.js b/backend/models/setting.js deleted file mode 100644 index 4a10f157e..000000000 --- a/backend/models/setting.js +++ /dev/null @@ -1,30 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; - -Model.knex(db); - -class Setting extends Model { - $beforeInsert() { - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - static get name() { - return 'Setting'; - } - - static get tableName() { - return 'setting'; - } - - static get jsonAttributes() { - return ['meta']; - } -} - -module.exports = Setting; diff --git a/backend/models/stream.js b/backend/models/stream.js deleted file mode 100644 index b7899d9b3..000000000 --- a/backend/models/stream.js +++ /dev/null @@ -1,55 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const now = require('./now_helper'); - -Model.knex(db); - -class Stream extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for meta - if (typeof this.meta === 'undefined') { - this.meta = {}; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'Stream'; - } - - static get tableName() { - return 'stream'; - } - - static get jsonAttributes() { - return ['meta']; - } - - static get relationMappings() { - return { - owner: { - relation: Model.HasOneRelation, - modelClass: User, - join: { - from: 'stream.owner_user_id', - to: 'user.id', - }, - modify: function (qb) { - qb.where('user.is_deleted', 0); - }, - }, - }; - } -} - -module.exports = Stream; diff --git a/backend/models/token.js b/backend/models/token.js deleted file mode 100644 index 5ce20d7cd..000000000 --- a/backend/models/token.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - NOTE: This is not a database table, this is a model of a Token object that can be created/loaded - and then has abilities after that. - */ - -const _ = require('lodash'); -const jwt = require('jsonwebtoken'); -const crypto = require('crypto'); -const config = require('../lib/config'); -const error = require('../lib/error'); -const logger = require('../logger').global; -const ALGO = 'RS256'; - -module.exports = function () { - let token_data = {}; - - const self = { - /** - * @param {Object} payload - * @returns {Promise} - */ - create: (payload) => { - if (!config.getPrivateKey()) { - logger.error('Private key is empty!'); - } - // sign with RSA SHA256 - const options = { - algorithm: ALGO, - expiresIn: payload.expiresIn || '1d', - }; - - payload.jti = crypto.randomBytes(12).toString('base64').substring(-8); - - return new Promise((resolve, reject) => { - jwt.sign(payload, config.getPrivateKey(), options, (err, token) => { - if (err) { - reject(err); - } else { - token_data = payload; - resolve({ - token, - payload, - }); - } - }); - }); - }, - - /** - * @param {String} token - * @returns {Promise} - */ - load: function (token) { - if (!config.getPublicKey()) { - logger.error('Public key is empty!'); - } - return new Promise((resolve, reject) => { - try { - if (!token || token === null || token === 'null') { - reject(new error.AuthError('Empty token')); - } else { - jwt.verify(token, config.getPublicKey(), { ignoreExpiration: false, algorithms: [ALGO] }, (err, result) => { - if (err) { - if (err.name === 'TokenExpiredError') { - reject(new error.AuthError('Token has expired', err)); - } else { - reject(err); - } - } else { - token_data = result; - resolve(token_data); - } - }); - } - } catch (err) { - reject(err); - } - }); - }, - - /** - * Does the token have the specified scope? - * - * @param {String} scope - * @returns {Boolean} - */ - hasScope: function (scope) { - return typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, scope) !== -1; - }, - - /** - * @param {String} key - * @return {*} - */ - get: function (key) { - if (typeof token_data[key] !== 'undefined') { - return token_data[key]; - } - - return null; - }, - - /** - * @param {String} key - * @param {*} value - */ - set: function (key, value) { - token_data[key] = value; - }, - - /** - * @param [default_value] - * @returns {Integer} - */ - getUserId: (default_value) => { - const attrs = self.get('attrs'); - if (attrs && typeof attrs.id !== 'undefined' && attrs.id) { - return attrs.id; - } - - return default_value || 0; - }, - }; - - return self; -}; diff --git a/backend/models/user.js b/backend/models/user.js deleted file mode 100644 index 5c4299993..000000000 --- a/backend/models/user.js +++ /dev/null @@ -1,52 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const UserPermission = require('./user_permission'); -const now = require('./now_helper'); - -Model.knex(db); - -class User extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - - // Default for roles - if (typeof this.roles === 'undefined') { - this.roles = []; - } - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'User'; - } - - static get tableName() { - return 'user'; - } - - static get jsonAttributes() { - return ['roles']; - } - - static get relationMappings() { - return { - permissions: { - relation: Model.HasOneRelation, - modelClass: UserPermission, - join: { - from: 'user.id', - to: 'user_permission.user_id', - }, - }, - }; - } -} - -module.exports = User; diff --git a/backend/models/user_permission.js b/backend/models/user_permission.js deleted file mode 100644 index e41508d46..000000000 --- a/backend/models/user_permission.js +++ /dev/null @@ -1,29 +0,0 @@ -// Objection Docs: -// http://vincit.github.io/objection.js/ - -const db = require('../db'); -const Model = require('objection').Model; -const now = require('./now_helper'); - -Model.knex(db); - -class UserPermission extends Model { - $beforeInsert() { - this.created_on = now(); - this.modified_on = now(); - } - - $beforeUpdate() { - this.modified_on = now(); - } - - static get name() { - return 'UserPermission'; - } - - static get tableName() { - return 'user_permission'; - } -} - -module.exports = UserPermission; diff --git a/backend/nodemon.json b/backend/nodemon.json deleted file mode 100644 index 3d6d13420..000000000 --- a/backend/nodemon.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "verbose": false, - "ignore": [ - "data" - ], - "ext": "js json ejs" -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 742b65f6d..000000000 --- a/backend/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "npmplus", - "version": "0.0.0", - "description": "A beautiful interface for creating Nginx endpoints", - "main": "index.js", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "11.5.5", - "ajv": "6.12.6", - "archiver": "7.0.1", - "batchflow": "0.4.0", - "bcrypt": "5.1.1", - "body-parser": "1.20.2", - "compression": "1.7.4", - "express": "4.19.2", - "express-fileupload": "1.5.0", - "gravatar": "1.8.2", - "jsonwebtoken": "9.0.2", - "knex": "3.1.0", - "liquidjs": "10.11.0", - "lodash": "4.17.21", - "moment": "2.30.1", - "mysql": "2.18.1", - "node-rsa": "1.1.1", - "objection": "3.1.4", - "path": "0.12.7", - "signale": "1.4.0", - "sqlite3": "5.1.6" - }, - "author": "Jamie Curnow and ZoeyVid ", - "license": "MIT", - "devDependencies": { - "@eslint/js": "9.1.1", - "eslint": "9.1.0", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-prettier": "5.1.3", - "globals": "15.0.0", - "prettier": "3.2.5" - } -} diff --git a/backend/password-reset.js b/backend/password-reset.js deleted file mode 100755 index c12df7b76..000000000 --- a/backend/password-reset.js +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node - -// based on: https://github.com/jlesage/docker-nginx-proxy-manager/blob/796734a3f9a87e0b1561b47fd418f82216359634/rootfs/opt/nginx-proxy-manager/bin/reset-password - -const fs = require('fs'); -const bcrypt = require('bcrypt'); -const sqlite3 = require('sqlite3'); - -function usage() { - console.log(`usage: node ${process.argv[1]} USER_EMAIL PASSWORD - -Reset password of a NPMplus user. - -Arguments: - USER_EMAIL Email address of the user to reset the password. - PASSWORD Optional new password of the user. If not set, password - is set to 'changeme'. -`); - process.exit(1); -} - -const args = process.argv.slice(2); - -const USER_EMAIL = args[0]; -if (!USER_EMAIL) { - console.error('ERROR: User email address must be set.'); - usage(); -} - -const PASSWORD = args[1]; -if (!PASSWORD) { - console.error('ERROR: Password must be set.'); - usage(); -} - -if (fs.existsSync(process.env.DB_SQLITE_FILE)) { - bcrypt.hash(PASSWORD, 13, (err, PASSWORD_HASH) => { - if (err) { - console.error(err); - process.exit(1); - } - - const db = new sqlite3.Database(process.env.DB_SQLITE_FILE); - db.run( - `UPDATE auth SET secret = ? WHERE EXISTS - (SELECT * FROM user WHERE user.id = auth.user_id AND user.email = ?)`, - [PASSWORD_HASH, USER_EMAIL], - function (err) { - if (err) { - console.error(err); - process.exit(1); - } - - console.log(`Password for user ${USER_EMAIL} has been reset.`); - process.exit(0); - }, - ); - }); -} diff --git a/backend/routes/api/audit-log.js b/backend/routes/api/audit-log.js deleted file mode 100644 index 02d8811e7..000000000 --- a/backend/routes/api/audit-log.js +++ /dev/null @@ -1,54 +0,0 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const internalAuditLog = require('../../internal/audit-log'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/audit-log - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/audit-log - * - * Retrieve all logs - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalAuditLog.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/main.js b/backend/routes/api/main.js deleted file mode 100644 index 25707ba1b..000000000 --- a/backend/routes/api/main.js +++ /dev/null @@ -1,51 +0,0 @@ -const express = require('express'); -const pjson = require('../../package.json'); -const error = require('../../lib/error'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * Health Check - * GET /api - */ -router.get('/', (req, res /*, next */) => { - const version = pjson.version.split('-').shift().split('.'); - - res.status(200).send({ - status: 'OK', - version: { - major: parseInt(version.shift(), 10), - minor: parseInt(version.shift(), 10), - revision: parseInt(version.shift(), 10), - }, - }); -}); - -router.use('/schema', require('./schema')); -router.use('/tokens', require('./tokens')); -router.use('/users', require('./users')); -router.use('/audit-log', require('./audit-log')); -router.use('/reports', require('./reports')); -router.use('/settings', require('./settings')); -router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); -router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); -router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); -router.use('/nginx/streams', require('./nginx/streams')); -router.use('/nginx/access-lists', require('./nginx/access_lists')); -router.use('/nginx/certificates', require('./nginx/certificates')); - -/** - * API 404 for all other routes - * - * ALL /api/* - */ -router.all(/(.+)/, function (req, res, next) { - req.params.page = req.params['0']; - next(new error.ItemNotFoundError(req.params.page)); -}); - -module.exports = router; diff --git a/backend/routes/api/nginx/access_lists.js b/backend/routes/api/nginx/access_lists.js deleted file mode 100644 index fb09e5dbf..000000000 --- a/backend/routes/api/nginx/access_lists.js +++ /dev/null @@ -1,150 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalAccessList = require('../../../internal/access-list'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/access-lists - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/access-lists - * - * Retrieve all access-lists - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalAccessList.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/access-lists - * - * Create a new access-list - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/access-lists#/links/1/schema' }, req.body) - .then((payload) => { - return internalAccessList.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific access-list - * - * /api/nginx/access-lists/123 - */ -router - .route('/:list_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/access-lists/123 - * - * Retrieve a specific access-list - */ - .get((req, res, next) => { - validator( - { - required: ['list_id'], - additionalProperties: false, - properties: { - list_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - list_id: req.params.list_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalAccessList.get(res.locals.access, { - id: parseInt(data.list_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/access-lists/123 - * - * Update and existing access-list - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/access-lists#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.list_id, 10); - return internalAccessList.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/access-lists/123 - * - * Delete and existing access-list - */ - .delete((req, res, next) => { - internalAccessList - .delete(res.locals.access, { id: parseInt(req.params.list_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/nginx/certificates.js b/backend/routes/api/nginx/certificates.js deleted file mode 100644 index dc89152d8..000000000 --- a/backend/routes/api/nginx/certificates.js +++ /dev/null @@ -1,299 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalCertificate = require('../../../internal/certificate'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/certificates - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates - * - * Retrieve all certificates - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalCertificate.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/certificates - * - * Create a new certificate - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/certificates#/links/1/schema' }, req.body) - .then((payload) => { - req.setTimeout(900000); // 15 minutes timeout - return internalCertificate.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Test HTTP challenge for domains - * - * /api/nginx/certificates/test-http - */ -router - .route('/test-http') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates/test-http - * - * Test HTTP challenge for domains - */ - .get((req, res, next) => { - internalCertificate - .testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Specific certificate - * - * /api/nginx/certificates/123 - */ -router - .route('/:certificate_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates/123 - * - * Retrieve a specific certificate - */ - .get((req, res, next) => { - validator( - { - required: ['certificate_id'], - additionalProperties: false, - properties: { - certificate_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - certificate_id: req.params.certificate_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalCertificate.get(res.locals.access, { - id: parseInt(data.certificate_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/certificates/123 - * - * Update and existing certificate - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/certificates#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.certificate_id, 10); - return internalCertificate.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/certificates/123 - * - * Update and existing certificate - */ - .delete((req, res, next) => { - internalCertificate - .delete(res.locals.access, { id: parseInt(req.params.certificate_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Upload Certs - * - * /api/nginx/certificates/123/upload - */ -router - .route('/:certificate_id/upload') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/123/upload - * - * Upload certificates - */ - .post((req, res, next) => { - if (!req.files) { - res.status(400).send({ error: 'No files were uploaded' }); - } else { - internalCertificate - .upload(res.locals.access, { - id: parseInt(req.params.certificate_id, 10), - files: req.files, - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - } - }); - -/** - * Renew Certbot Certs - * - * /api/nginx/certificates/123/renew - */ -router - .route('/:certificate_id/renew') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/123/renew - * - * Renew certificate - */ - .post((req, res, next) => { - req.setTimeout(900000); // 15 minutes timeout - internalCertificate - .renew(res.locals.access, { - id: parseInt(req.params.certificate_id, 10), - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Download Certbot Certs - * - * /api/nginx/certificates/123/download - */ -router - .route('/:certificate_id/download') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/certificates/123/download - * - * Renew certificate - */ - .get((req, res, next) => { - internalCertificate - .download(res.locals.access, { - id: parseInt(req.params.certificate_id, 10), - }) - .then((result) => { - res.status(200).download(result.fileName); - }) - .catch(next); - }); - -/** - * Validate Certs before saving - * - * /api/nginx/certificates/validate - */ -router - .route('/validate') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/certificates/validate - * - * Validate certificates - */ - .post((req, res, next) => { - if (!req.files) { - res.status(400).send({ error: 'No files were uploaded' }); - } else { - internalCertificate - .validate({ - files: req.files, - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - } - }); - -module.exports = router; diff --git a/backend/routes/api/nginx/dead_hosts.js b/backend/routes/api/nginx/dead_hosts.js deleted file mode 100644 index 43f406dc1..000000000 --- a/backend/routes/api/nginx/dead_hosts.js +++ /dev/null @@ -1,198 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalDeadHost = require('../../../internal/dead-host'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/dead-hosts - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/dead-hosts - * - * Retrieve all dead-hosts - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalDeadHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/dead-hosts - * - * Create a new dead-host - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/dead-hosts#/links/1/schema' }, req.body) - .then((payload) => { - return internalDeadHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific dead-host - * - * /api/nginx/dead-hosts/123 - */ -router - .route('/:host_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/dead-hosts/123 - * - * Retrieve a specific dead-host - */ - .get((req, res, next) => { - validator( - { - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - host_id: req.params.host_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalDeadHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/dead-hosts/123 - * - * Update and existing dead-host - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/dead-hosts#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalDeadHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/dead-hosts/123 - * - * Update and existing dead-host - */ - .delete((req, res, next) => { - internalDeadHost - .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Enable dead-host - * - * /api/nginx/dead-hosts/123/enable - */ -router - .route('/:host_id/enable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/dead-hosts/123/enable - */ - .post((req, res, next) => { - internalDeadHost - .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Disable dead-host - * - * /api/nginx/dead-hosts/123/disable - */ -router - .route('/:host_id/disable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/dead-hosts/123/disable - */ - .post((req, res, next) => { - internalDeadHost - .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/nginx/proxy_hosts.js b/backend/routes/api/nginx/proxy_hosts.js deleted file mode 100644 index b3afa5df8..000000000 --- a/backend/routes/api/nginx/proxy_hosts.js +++ /dev/null @@ -1,198 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalProxyHost = require('../../../internal/proxy-host'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/proxy-hosts - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/proxy-hosts - * - * Retrieve all proxy-hosts - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalProxyHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/proxy-hosts - * - * Create a new proxy-host - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/proxy-hosts#/links/1/schema' }, req.body) - .then((payload) => { - return internalProxyHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific proxy-host - * - * /api/nginx/proxy-hosts/123 - */ -router - .route('/:host_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/proxy-hosts/123 - * - * Retrieve a specific proxy-host - */ - .get((req, res, next) => { - validator( - { - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - host_id: req.params.host_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalProxyHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/proxy-hosts/123 - * - * Update and existing proxy-host - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/proxy-hosts#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalProxyHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/proxy-hosts/123 - * - * Update and existing proxy-host - */ - .delete((req, res, next) => { - internalProxyHost - .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Enable proxy-host - * - * /api/nginx/proxy-hosts/123/enable - */ -router - .route('/:host_id/enable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/proxy-hosts/123/enable - */ - .post((req, res, next) => { - internalProxyHost - .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Disable proxy-host - * - * /api/nginx/proxy-hosts/123/disable - */ -router - .route('/:host_id/disable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/proxy-hosts/123/disable - */ - .post((req, res, next) => { - internalProxyHost - .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/nginx/redirection_hosts.js b/backend/routes/api/nginx/redirection_hosts.js deleted file mode 100644 index e069308c9..000000000 --- a/backend/routes/api/nginx/redirection_hosts.js +++ /dev/null @@ -1,198 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalRedirectionHost = require('../../../internal/redirection-host'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/redirection-hosts - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/redirection-hosts - * - * Retrieve all redirection-hosts - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/redirection-hosts - * - * Create a new redirection-host - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/redirection-hosts#/links/1/schema' }, req.body) - .then((payload) => { - return internalRedirectionHost.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific redirection-host - * - * /api/nginx/redirection-hosts/123 - */ -router - .route('/:host_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/nginx/redirection-hosts/123 - * - * Retrieve a specific redirection-host - */ - .get((req, res, next) => { - validator( - { - required: ['host_id'], - additionalProperties: false, - properties: { - host_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - host_id: req.params.host_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalRedirectionHost.get(res.locals.access, { - id: parseInt(data.host_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/redirection-hosts/123 - * - * Update and existing redirection-host - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/redirection-hosts#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.host_id, 10); - return internalRedirectionHost.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/redirection-hosts/123 - * - * Update and existing redirection-host - */ - .delete((req, res, next) => { - internalRedirectionHost - .delete(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Enable redirection-host - * - * /api/nginx/redirection-hosts/123/enable - */ -router - .route('/:host_id/enable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/redirection-hosts/123/enable - */ - .post((req, res, next) => { - internalRedirectionHost - .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Disable redirection-host - * - * /api/nginx/redirection-hosts/123/disable - */ -router - .route('/:host_id/disable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/redirection-hosts/123/disable - */ - .post((req, res, next) => { - internalRedirectionHost - .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/nginx/streams.js b/backend/routes/api/nginx/streams.js deleted file mode 100644 index 24670306a..000000000 --- a/backend/routes/api/nginx/streams.js +++ /dev/null @@ -1,198 +0,0 @@ -const express = require('express'); -const validator = require('../../../lib/validator'); -const jwtdecode = require('../../../lib/express/jwt-decode'); -const internalStream = require('../../../internal/stream'); -const apiValidator = require('../../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/nginx/streams - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes - - /** - * GET /api/nginx/streams - * - * Retrieve all streams - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalStream.getAll(res.locals.access, data.expand, data.query); - }) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }) - - /** - * POST /api/nginx/streams - * - * Create a new stream - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/streams#/links/1/schema' }, req.body) - .then((payload) => { - return internalStream.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific stream - * - * /api/nginx/streams/123 - */ -router - .route('/:stream_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes - - /** - * GET /api/nginx/streams/123 - * - * Retrieve a specific stream - */ - .get((req, res, next) => { - validator( - { - required: ['stream_id'], - additionalProperties: false, - properties: { - stream_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - stream_id: req.params.stream_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalStream.get(res.locals.access, { - id: parseInt(data.stream_id, 10), - expand: data.expand, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/nginx/streams/123 - * - * Update and existing stream - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/streams#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = parseInt(req.params.stream_id, 10); - return internalStream.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/nginx/streams/123 - * - * Update and existing stream - */ - .delete((req, res, next) => { - internalStream - .delete(res.locals.access, { id: parseInt(req.params.stream_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Enable stream - * - * /api/nginx/streams/123/enable - */ -router - .route('/:host_id/enable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/streams/123/enable - */ - .post((req, res, next) => { - internalStream - .enable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Disable stream - * - * /api/nginx/streams/123/disable - */ -router - .route('/:host_id/disable') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/nginx/streams/123/disable - */ - .post((req, res, next) => { - internalStream - .disable(res.locals.access, { id: parseInt(req.params.host_id, 10) }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/reports.js b/backend/routes/api/reports.js deleted file mode 100644 index 820ac1173..000000000 --- a/backend/routes/api/reports.js +++ /dev/null @@ -1,29 +0,0 @@ -const express = require('express'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const internalReport = require('../../internal/report'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -router - .route('/hosts') - .options((req, res) => { - res.sendStatus(204); - }) - - /** - * GET /reports/hosts - */ - .get(jwtdecode(), (req, res, next) => { - internalReport - .getHostsReport(res.locals.access) - .then((data) => { - res.status(200).send(data); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/schema.js b/backend/routes/api/schema.js deleted file mode 100644 index 446330174..000000000 --- a/backend/routes/api/schema.js +++ /dev/null @@ -1,36 +0,0 @@ -const express = require('express'); -const swaggerJSON = require('../../doc/api.swagger.json'); -const PACKAGE = require('../../package.json'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - - /** - * GET /schema - */ - .get((req, res /*, next */) => { - let proto = req.protocol; - if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) { - proto = req.headers['x-forwarded-proto']; - } - - let origin = proto + '://' + req.hostname; - if (typeof req.headers.origin !== 'undefined' && req.headers.origin) { - origin = req.headers.origin; - } - - swaggerJSON.info.version = PACKAGE.version; - swaggerJSON.servers[0].url = origin + '/api'; - res.status(200).send(swaggerJSON); - }); - -module.exports = router; diff --git a/backend/routes/api/settings.js b/backend/routes/api/settings.js deleted file mode 100644 index 2b5afc18b..000000000 --- a/backend/routes/api/settings.js +++ /dev/null @@ -1,97 +0,0 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const internalSetting = require('../../internal/setting'); -const apiValidator = require('../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/settings - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/settings - * - * Retrieve all settings - */ - .get((req, res, next) => { - internalSetting - .getAll(res.locals.access) - .then((rows) => { - res.status(200).send(rows); - }) - .catch(next); - }); - -/** - * Specific setting - * - * /api/settings/something - */ -router - .route('/:setting_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /settings/something - * - * Retrieve a specific setting - */ - .get((req, res, next) => { - validator( - { - required: ['setting_id'], - additionalProperties: false, - properties: { - setting_id: { - $ref: 'definitions#/definitions/setting_id', - }, - }, - }, - { - setting_id: req.params.setting_id, - }, - ) - .then((data) => { - return internalSetting.get(res.locals.access, { - id: data.setting_id, - }); - }) - .then((row) => { - res.status(200).send(row); - }) - .catch(next); - }) - - /** - * PUT /api/settings/something - * - * Update and existing setting - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/settings#/links/1/schema' }, req.body) - .then((payload) => { - payload.id = req.params.setting_id; - return internalSetting.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/tokens.js b/backend/routes/api/tokens.js deleted file mode 100644 index 031a6b2e6..000000000 --- a/backend/routes/api/tokens.js +++ /dev/null @@ -1,53 +0,0 @@ -const express = require('express'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const internalToken = require('../../internal/token'); -const apiValidator = require('../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - - /** - * GET /tokens - * - * Get a new Token, given they already have a token they want to refresh - * We also piggy back on to this method, allowing admins to get tokens - * for services like Job board and Worker. - */ - .get(jwtdecode(), (req, res, next) => { - internalToken - .getFreshToken(res.locals.access, { - expiry: typeof req.query.expiry !== 'undefined' ? req.query.expiry : null, - scope: typeof req.query.scope !== 'undefined' ? req.query.scope : null, - }) - .then((data) => { - res.status(200).send(data); - }) - .catch(next); - }) - - /** - * POST /tokens - * - * Create a new Token - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/tokens#/links/0/schema' }, req.body) - .then((payload) => { - return internalToken.getTokenFromEmail(payload); - }) - .then((data) => { - res.status(200).send(data); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/routes/api/users.js b/backend/routes/api/users.js deleted file mode 100644 index fd186df11..000000000 --- a/backend/routes/api/users.js +++ /dev/null @@ -1,239 +0,0 @@ -const express = require('express'); -const validator = require('../../lib/validator'); -const jwtdecode = require('../../lib/express/jwt-decode'); -const userIdFromMe = require('../../lib/express/user-id-from-me'); -const internalUser = require('../../internal/user'); -const apiValidator = require('../../lib/validator/api'); - -const router = express.Router({ - caseSensitive: true, - strict: true, - mergeParams: true, -}); - -/** - * /api/users - */ -router - .route('/') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * GET /api/users - * - * Retrieve all users - */ - .get((req, res, next) => { - validator( - { - additionalProperties: false, - properties: { - expand: { - $ref: 'definitions#/definitions/expand', - }, - query: { - $ref: 'definitions#/definitions/query', - }, - }, - }, - { - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - query: typeof req.query.query === 'string' ? req.query.query : null, - }, - ) - .then((data) => { - return internalUser.getAll(res.locals.access, data.expand, data.query); - }) - .then((users) => { - res.status(200).send(users); - }) - .catch(next); - }) - - /** - * POST /api/users - * - * Create a new User - */ - .post((req, res, next) => { - apiValidator({ $ref: 'endpoints/users#/links/1/schema' }, req.body) - .then((payload) => { - return internalUser.create(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific user - * - * /api/users/123 - */ -router - .route('/:user_id') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - .all(userIdFromMe) - - /** - * GET /users/123 or /users/me - * - * Retrieve a specific user - */ - .get((req, res, next) => { - validator( - { - required: ['user_id'], - additionalProperties: false, - properties: { - user_id: { - $ref: 'definitions#/definitions/id', - }, - expand: { - $ref: 'definitions#/definitions/expand', - }, - }, - }, - { - user_id: req.params.user_id, - expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null, - }, - ) - .then((data) => { - return internalUser.get(res.locals.access, { - id: data.user_id, - expand: data.expand, - omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id), - }); - }) - .then((user) => { - res.status(200).send(user); - }) - .catch(next); - }) - - /** - * PUT /api/users/123 - * - * Update and existing user - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/users#/links/2/schema' }, req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.update(res.locals.access, payload); - }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }) - - /** - * DELETE /api/users/123 - * - * Update and existing user - */ - .delete((req, res, next) => { - internalUser - .delete(res.locals.access, { id: req.params.user_id }) - .then((result) => { - res.status(200).send(result); - }) - .catch(next); - }); - -/** - * Specific user auth - * - * /api/users/123/auth - */ -router - .route('/:user_id/auth') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - .all(userIdFromMe) - - /** - * PUT /api/users/123/auth - * - * Update password for a user - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/users#/links/4/schema' }, req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.setPassword(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific user permissions - * - * /api/users/123/permissions - */ -router - .route('/:user_id/permissions') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - .all(userIdFromMe) - - /** - * PUT /api/users/123/permissions - * - * Set some or all permissions for a user - */ - .put((req, res, next) => { - apiValidator({ $ref: 'endpoints/users#/links/5/schema' }, req.body) - .then((payload) => { - payload.id = req.params.user_id; - return internalUser.setPermissions(res.locals.access, payload); - }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -/** - * Specific user login as - * - * /api/users/123/login - */ -router - .route('/:user_id/login') - .options((req, res) => { - res.sendStatus(204); - }) - .all(jwtdecode()) - - /** - * POST /api/users/123/login - * - * Log in as a user - */ - .post((req, res, next) => { - internalUser - .loginAs(res.locals.access, { id: parseInt(req.params.user_id, 10) }) - .then((result) => { - res.status(201).send(result); - }) - .catch(next); - }); - -module.exports = router; diff --git a/backend/schema/definitions.json b/backend/schema/definitions.json deleted file mode 100644 index 7f5b4dd20..000000000 --- a/backend/schema/definitions.json +++ /dev/null @@ -1,240 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "definitions", - "definitions": { - "id": { - "description": "Unique identifier", - "example": 123456, - "readOnly": true, - "type": "integer", - "minimum": 1 - }, - "setting_id": { - "description": "Unique identifier for a Setting", - "example": "default-site", - "readOnly": true, - "type": "string", - "minLength": 2 - }, - "token": { - "type": "string", - "minLength": 10 - }, - "expand": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ] - }, - "sort": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "field", - "dir" - ], - "additionalProperties": false, - "properties": { - "field": { - "type": "string" - }, - "dir": { - "type": "string", - "pattern": "^(asc|desc)$" - } - } - } - }, - "query": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "string", - "minLength": 1, - "maxLength": 255 - } - ] - }, - "criteria": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "object" - } - ] - }, - "fields": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ] - }, - "omit": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ] - }, - "created_on": { - "description": "Date and time of creation", - "format": "date-time", - "readOnly": true, - "type": "string" - }, - "modified_on": { - "description": "Date and time of last update", - "format": "date-time", - "readOnly": true, - "type": "string" - }, - "user_id": { - "description": "User ID", - "example": 1234, - "type": "integer", - "minimum": 1 - }, - "certificate_id": { - "description": "Certificate ID", - "example": 1234, - "anyOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "string", - "pattern": "^new$" - } - ] - }, - "access_list_id": { - "description": "Access List ID", - "example": 1234, - "type": "integer", - "minimum": 0 - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 255 - }, - "email": { - "description": "Email Address", - "example": "john@example.com", - "format": "email", - "type": "string", - "minLength": 6, - "maxLength": 100 - }, - "password": { - "description": "Password", - "type": "string", - "minLength": 8, - "maxLength": 255 - }, - "domain_name": { - "description": "Domain Name", - "example": "jc21.com", - "type": "string", - "pattern": "^(?:[^.*]+\\.?)+[^.]$" - }, - "domain_names": { - "description": "Domain Names separated by a comma", - "example": "*.jc21.com,blog.jc21.com", - "type": "array", - "maxItems": 99, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$" - } - }, - "http_code": { - "description": "Redirect HTTP Status Code", - "example": 302, - "type": "integer", - "minimum": 300, - "maximum": 308 - }, - "scheme": { - "description": "RFC Protocol", - "example": "HTTPS or $scheme", - "type": "string", - "minLength": 4 - }, - "enabled": { - "description": "Is Enabled", - "example": true, - "type": "boolean" - }, - "ssl_enabled": { - "description": "Is SSL Enabled", - "example": true, - "type": "boolean" - }, - "ssl_forced": { - "description": "Is SSL Forced", - "example": false, - "type": "boolean" - }, - "hsts_enabled": { - "description": "Is HSTS Enabled", - "example": false, - "type": "boolean" - }, - "hsts_subdomains": { - "description": "Is HSTS applicable to all subdomains", - "example": false, - "type": "boolean" - }, - "ssl_provider": { - "type": "string", - "pattern": "^(letsencrypt|other)$" - }, - "http2_support": { - "description": "HTTP2 Protocol Support", - "example": false, - "type": "boolean" - }, - "block_exploits": { - "description": "Should we block common exploits", - "example": true, - "type": "boolean" - }, - "caching_enabled": { - "description": "Should we cache assets", - "example": true, - "type": "boolean" - } - } -} diff --git a/backend/schema/endpoints/access-lists.json b/backend/schema/endpoints/access-lists.json deleted file mode 100644 index 404e32376..000000000 --- a/backend/schema/endpoints/access-lists.json +++ /dev/null @@ -1,236 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/access-lists", - "title": "Access Lists", - "description": "Endpoints relating to Access Lists", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "name": { - "type": "string", - "description": "Name of the Access List" - }, - "directive": { - "type": "string", - "enum": ["allow", "deny"] - }, - "address": { - "oneOf": [ - { - "type": "string", - "pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" - }, - { - "type": "string", - "pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$" - }, - { - "type": "string", - "pattern": "^all$" - } - ] - }, - "satisfy_any": { - "type": "boolean" - }, - "pass_auth": { - "type": "boolean" - }, - "meta": { - "type": "object" - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "name": { - "$ref": "#/definitions/name" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Access Lists", - "href": "/nginx/access-lists", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new Access List", - "href": "/nginx/access-list", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": ["name"], - "properties": { - "name": { - "$ref": "#/definitions/name" - }, - "satisfy_any": { - "$ref": "#/definitions/satisfy_any" - }, - "pass_auth": { - "$ref": "#/definitions/pass_auth" - }, - "items": { - "type": "array", - "minItems": 0, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string", - "minLength": 1 - }, - "password": { - "type": "string", - "minLength": 1 - } - } - } - }, - "clients": { - "type": "array", - "minItems": 0, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "address": { - "$ref": "#/definitions/address" - }, - "directive": { - "$ref": "#/definitions/directive" - } - } - } - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing Access List", - "href": "/nginx/access-list/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "$ref": "#/definitions/name" - }, - "satisfy_any": { - "$ref": "#/definitions/satisfy_any" - }, - "pass_auth": { - "$ref": "#/definitions/pass_auth" - }, - "items": { - "type": "array", - "minItems": 0, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "username": { - "type": "string", - "minLength": 1 - }, - "password": { - "type": "string", - "minLength": 0 - } - } - } - }, - "clients": { - "type": "array", - "minItems": 0, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "address": { - "$ref": "#/definitions/address" - }, - "directive": { - "$ref": "#/definitions/directive" - } - } - } - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing Access List", - "href": "/nginx/access-list/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - } - ] -} diff --git a/backend/schema/endpoints/certificates.json b/backend/schema/endpoints/certificates.json deleted file mode 100644 index aea3e43c7..000000000 --- a/backend/schema/endpoints/certificates.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/certificates", - "title": "Certificates", - "description": "Endpoints relating to Certificates", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "provider": { - "$ref": "../definitions.json#/definitions/ssl_provider" - }, - "nice_name": { - "type": "string", - "description": "Nice Name for the custom certificate" - }, - "domain_names": { - "$ref": "../definitions.json#/definitions/domain_names" - }, - "expires_on": { - "description": "Date and time of expiration", - "format": "date-time", - "readOnly": true, - "type": "string" - }, - "meta": { - "type": "object", - "additionalProperties": false, - "properties": { - "letsencrypt_email": { - "type": "string", - "format": "email" - }, - "letsencrypt_agree": { - "type": "boolean" - }, - "dns_challenge": { - "type": "boolean" - }, - "dns_provider": { - "type": "string" - }, - "dns_provider_credentials": { - "type": "string" - }, - "propagation_seconds": { - "anyOf": [ - { - "type": "integer", - "minimum": 0 - } - ] - - } - } - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "provider": { - "$ref": "#/definitions/provider" - }, - "nice_name": { - "$ref": "#/definitions/nice_name" - }, - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "expires_on": { - "$ref": "#/definitions/expires_on" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Certificates", - "href": "/nginx/certificates", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new Certificate", - "href": "/nginx/certificates", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "provider" - ], - "properties": { - "provider": { - "$ref": "#/definitions/provider" - }, - "nice_name": { - "$ref": "#/definitions/nice_name" - }, - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing Certificate", - "href": "/nginx/certificates/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Test HTTP Challenge", - "description": "Tests whether the HTTP challenge should work", - "href": "/nginx/certificates/{definitions.identity.example}/test-http", - "access": "private", - "method": "GET", - "rel": "info", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - } - } - ] -} diff --git a/backend/schema/endpoints/dead-hosts.json b/backend/schema/endpoints/dead-hosts.json deleted file mode 100644 index 0c73c3be1..000000000 --- a/backend/schema/endpoints/dead-hosts.json +++ /dev/null @@ -1,240 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/dead-hosts", - "title": "404 Hosts", - "description": "Endpoints relating to 404 Hosts", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "domain_names": { - "$ref": "../definitions.json#/definitions/domain_names" - }, - "certificate_id": { - "$ref": "../definitions.json#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "../definitions.json#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "../definitions.json#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "../definitions.json#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "../definitions.json#/definitions/http2_support" - }, - "advanced_config": { - "type": "string" - }, - "enabled": { - "$ref": "../definitions.json#/definitions/enabled" - }, - "meta": { - "type": "object" - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of 404 Hosts", - "href": "/nginx/dead-hosts", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new 404 Host", - "href": "/nginx/dead-hosts", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "domain_names" - ], - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing 404 Host", - "href": "/nginx/dead-hosts/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing 404 Host", - "href": "/nginx/dead-hosts/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Enable", - "description": "Enables a existing 404 Host", - "href": "/nginx/dead-hosts/{definitions.identity.example}/enable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Disable", - "description": "Disables a existing 404 Host", - "href": "/nginx/dead-hosts/{definitions.identity.example}/disable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - } - ] -} diff --git a/backend/schema/endpoints/proxy-hosts.json b/backend/schema/endpoints/proxy-hosts.json deleted file mode 100644 index 9a3fff2fc..000000000 --- a/backend/schema/endpoints/proxy-hosts.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/proxy-hosts", - "title": "Proxy Hosts", - "description": "Endpoints relating to Proxy Hosts", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "domain_names": { - "$ref": "../definitions.json#/definitions/domain_names" - }, - "forward_scheme": { - "type": "string", - "enum": ["http", "https"] - }, - "forward_host": { - "type": "string", - "minLength": 1, - "maxLength": 255 - }, - "forward_port": { - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "certificate_id": { - "$ref": "../definitions.json#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "../definitions.json#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "../definitions.json#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "../definitions.json#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "../definitions.json#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "../definitions.json#/definitions/block_exploits" - }, - "caching_enabled": { - "$ref": "../definitions.json#/definitions/caching_enabled" - }, - "allow_websocket_upgrade": { - "description": "Allow Websocket Upgrade for all paths", - "example": true, - "type": "boolean" - }, - "access_list_id": { - "$ref": "../definitions.json#/definitions/access_list_id" - }, - "advanced_config": { - "type": "string" - }, - "enabled": { - "$ref": "../definitions.json#/definitions/enabled" - }, - "meta": { - "type": "object" - }, - "locations": { - "type": "array", - "minItems": 0, - "items": { - "type": "object", - "required": [ - "forward_scheme", - "forward_host", - "forward_port", - "path" - ], - "additionalProperties": false, - "properties": { - "id": { - "type": ["integer", "null"] - }, - "path": { - "type": "string", - "minLength": 1 - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_host": { - "$ref": "#/definitions/forward_host" - }, - "forward_port": { - "$ref": "#/definitions/forward_port" - }, - "forward_path": { - "type": "string" - }, - "advanced_config": { - "type": "string" - } - } - } - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_host": { - "$ref": "#/definitions/forward_host" - }, - "forward_port": { - "$ref": "#/definitions/forward_port" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "caching_enabled": { - "$ref": "#/definitions/caching_enabled" - }, - "allow_websocket_upgrade": { - "$ref": "#/definitions/allow_websocket_upgrade" - }, - "access_list_id": { - "$ref": "#/definitions/access_list_id" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "locations": { - "$ref": "#/definitions/locations" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Proxy Hosts", - "href": "/nginx/proxy-hosts", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new Proxy Host", - "href": "/nginx/proxy-hosts", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "domain_names", - "forward_scheme", - "forward_host", - "forward_port" - ], - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_host": { - "$ref": "#/definitions/forward_host" - }, - "forward_port": { - "$ref": "#/definitions/forward_port" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "caching_enabled": { - "$ref": "#/definitions/caching_enabled" - }, - "allow_websocket_upgrade": { - "$ref": "#/definitions/allow_websocket_upgrade" - }, - "access_list_id": { - "$ref": "#/definitions/access_list_id" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "locations": { - "$ref": "#/definitions/locations" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing Proxy Host", - "href": "/nginx/proxy-hosts/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_host": { - "$ref": "#/definitions/forward_host" - }, - "forward_port": { - "$ref": "#/definitions/forward_port" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "caching_enabled": { - "$ref": "#/definitions/caching_enabled" - }, - "allow_websocket_upgrade": { - "$ref": "#/definitions/allow_websocket_upgrade" - }, - "access_list_id": { - "$ref": "#/definitions/access_list_id" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "locations": { - "$ref": "#/definitions/locations" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing Proxy Host", - "href": "/nginx/proxy-hosts/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Enable", - "description": "Enables a existing Proxy Host", - "href": "/nginx/proxy-hosts/{definitions.identity.example}/enable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Disable", - "description": "Disables a existing Proxy Host", - "href": "/nginx/proxy-hosts/{definitions.identity.example}/disable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - } - ] -} diff --git a/backend/schema/endpoints/redirection-hosts.json b/backend/schema/endpoints/redirection-hosts.json deleted file mode 100644 index 14a469985..000000000 --- a/backend/schema/endpoints/redirection-hosts.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/redirection-hosts", - "title": "Redirection Hosts", - "description": "Endpoints relating to Redirection Hosts", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "domain_names": { - "$ref": "../definitions.json#/definitions/domain_names" - }, - "forward_http_code": { - "$ref": "../definitions.json#/definitions/http_code" - }, - "forward_scheme": { - "$ref": "../definitions.json#/definitions/scheme" - }, - "forward_domain_name": { - "$ref": "../definitions.json#/definitions/domain_name" - }, - "preserve_path": { - "description": "Should the path be preserved", - "example": true, - "type": "boolean" - }, - "certificate_id": { - "$ref": "../definitions.json#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "../definitions.json#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "../definitions.json#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "../definitions.json#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "../definitions.json#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "../definitions.json#/definitions/block_exploits" - }, - "advanced_config": { - "type": "string" - }, - "enabled": { - "$ref": "../definitions.json#/definitions/enabled" - }, - "meta": { - "type": "object" - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_http_code": { - "$ref": "#/definitions/forward_http_code" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_domain_name": { - "$ref": "#/definitions/forward_domain_name" - }, - "preserve_path": { - "$ref": "#/definitions/preserve_path" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_subdomains" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Redirection Hosts", - "href": "/nginx/redirection-hosts", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new Redirection Host", - "href": "/nginx/redirection-hosts", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "domain_names", - "forward_scheme", - "forward_http_code", - "forward_domain_name" - ], - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_http_code": { - "$ref": "#/definitions/forward_http_code" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_domain_name": { - "$ref": "#/definitions/forward_domain_name" - }, - "preserve_path": { - "$ref": "#/definitions/preserve_path" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing Redirection Host", - "href": "/nginx/redirection-hosts/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "domain_names": { - "$ref": "#/definitions/domain_names" - }, - "forward_http_code": { - "$ref": "#/definitions/forward_http_code" - }, - "forward_scheme": { - "$ref": "#/definitions/forward_scheme" - }, - "forward_domain_name": { - "$ref": "#/definitions/forward_domain_name" - }, - "preserve_path": { - "$ref": "#/definitions/preserve_path" - }, - "certificate_id": { - "$ref": "#/definitions/certificate_id" - }, - "ssl_forced": { - "$ref": "#/definitions/ssl_forced" - }, - "hsts_enabled": { - "$ref": "#/definitions/hsts_enabled" - }, - "hsts_subdomains": { - "$ref": "#/definitions/hsts_enabled" - }, - "http2_support": { - "$ref": "#/definitions/http2_support" - }, - "block_exploits": { - "$ref": "#/definitions/block_exploits" - }, - "advanced_config": { - "$ref": "#/definitions/advanced_config" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing Redirection Host", - "href": "/nginx/redirection-hosts/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Enable", - "description": "Enables a existing Redirection Host", - "href": "/nginx/redirection-hosts/{definitions.identity.example}/enable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Disable", - "description": "Disables a existing Redirection Host", - "href": "/nginx/redirection-hosts/{definitions.identity.example}/disable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - } - ] -} diff --git a/backend/schema/endpoints/settings.json b/backend/schema/endpoints/settings.json deleted file mode 100644 index 29e2865ae..000000000 --- a/backend/schema/endpoints/settings.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/settings", - "title": "Settings", - "description": "Endpoints relating to Settings", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/setting_id" - }, - "name": { - "description": "Name", - "example": "Default Site", - "type": "string", - "minLength": 2, - "maxLength": 100 - }, - "description": { - "description": "Description", - "example": "Default Site", - "type": "string", - "minLength": 2, - "maxLength": 255 - }, - "value": { - "description": "Value", - "example": "404", - "type": "string", - "maxLength": 255 - }, - "meta": { - "type": "object" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Settings", - "href": "/settings", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing Setting", - "href": "/settings/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "properties": { - "value": { - "$ref": "#/definitions/value" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - } - ], - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "name": { - "$ref": "#/definitions/description" - }, - "description": { - "$ref": "#/definitions/description" - }, - "value": { - "$ref": "#/definitions/value" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } -} diff --git a/backend/schema/endpoints/streams.json b/backend/schema/endpoints/streams.json deleted file mode 100644 index c52fec34f..000000000 --- a/backend/schema/endpoints/streams.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/streams", - "title": "Streams", - "description": "Endpoints relating to Streams", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "incoming_port": { - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "forwarding_host": { - "anyOf": [ - { - "$ref": "../definitions.json#/definitions/domain_name" - }, - { - "type": "string", - "format": "ipv4" - }, - { - "type": "string", - "format": "ipv6" - } - ] - }, - "forwarding_port": { - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "tcp_forwarding": { - "type": "boolean" - }, - "udp_forwarding": { - "type": "boolean" - }, - "enabled": { - "$ref": "../definitions.json#/definitions/enabled" - }, - "meta": { - "type": "object" - } - }, - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "incoming_port": { - "$ref": "#/definitions/incoming_port" - }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" - }, - "forwarding_port": { - "$ref": "#/definitions/forwarding_port" - }, - "tcp_forwarding": { - "$ref": "#/definitions/tcp_forwarding" - }, - "udp_forwarding": { - "$ref": "#/definitions/udp_forwarding" - }, - "enabled": { - "$ref": "#/definitions/enabled" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Streams", - "href": "/nginx/streams", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new Stream", - "href": "/nginx/streams", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "required": [ - "incoming_port", - "forwarding_host", - "forwarding_port" - ], - "properties": { - "incoming_port": { - "$ref": "#/definitions/incoming_port" - }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" - }, - "forwarding_port": { - "$ref": "#/definitions/forwarding_port" - }, - "tcp_forwarding": { - "$ref": "#/definitions/tcp_forwarding" - }, - "udp_forwarding": { - "$ref": "#/definitions/udp_forwarding" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing Stream", - "href": "/nginx/streams/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "incoming_port": { - "$ref": "#/definitions/incoming_port" - }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" - }, - "forwarding_port": { - "$ref": "#/definitions/forwarding_port" - }, - "tcp_forwarding": { - "$ref": "#/definitions/tcp_forwarding" - }, - "udp_forwarding": { - "$ref": "#/definitions/udp_forwarding" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing Stream", - "href": "/nginx/streams/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Enable", - "description": "Enables a existing Stream", - "href": "/nginx/streams/{definitions.identity.example}/enable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Disable", - "description": "Disables a existing Stream", - "href": "/nginx/streams/{definitions.identity.example}/disable", - "access": "private", - "method": "POST", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - } - ] -} diff --git a/backend/schema/endpoints/tokens.json b/backend/schema/endpoints/tokens.json deleted file mode 100644 index 920af63f4..000000000 --- a/backend/schema/endpoints/tokens.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/tokens", - "title": "Token", - "description": "Tokens are required to authenticate against the API", - "stability": "stable", - "type": "object", - "definitions": { - "identity": { - "description": "Email Address or other 3rd party providers identifier", - "example": "john@example.com", - "type": "string" - }, - "secret": { - "description": "A password or key", - "example": "correct horse battery staple", - "type": "string" - }, - "token": { - "description": "JWT", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk", - "type": "string" - }, - "expires": { - "description": "Token expiry time", - "format": "date-time", - "type": "string" - }, - "scope": { - "description": "Scope of the Token, defaults to 'user'", - "example": "user", - "type": "string" - } - }, - "links": [ - { - "title": "Create", - "description": "Creates a new token.", - "href": "/tokens", - "access": "public", - "method": "POST", - "rel": "create", - "schema": { - "type": "object", - "required": [ - "identity", - "secret" - ], - "properties": { - "identity": { - "$ref": "#/definitions/identity" - }, - "secret": { - "$ref": "#/definitions/secret" - }, - "scope": { - "$ref": "#/definitions/scope" - } - } - }, - "targetSchema": { - "type": "object", - "properties": { - "token": { - "$ref": "#/definitions/token" - }, - "expires": { - "$ref": "#/definitions/expires" - } - } - } - }, - { - "title": "Refresh", - "description": "Returns a new token.", - "href": "/tokens", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": {}, - "targetSchema": { - "type": "object", - "properties": { - "token": { - "$ref": "#/definitions/token" - }, - "expires": { - "$ref": "#/definitions/expires" - }, - "scope": { - "$ref": "#/definitions/scope" - } - } - } - } - ] -} diff --git a/backend/schema/endpoints/users.json b/backend/schema/endpoints/users.json deleted file mode 100644 index 5adff9025..000000000 --- a/backend/schema/endpoints/users.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/users", - "title": "Users", - "description": "Endpoints relating to Users", - "stability": "stable", - "type": "object", - "definitions": { - "id": { - "$ref": "../definitions.json#/definitions/id" - }, - "created_on": { - "$ref": "../definitions.json#/definitions/created_on" - }, - "modified_on": { - "$ref": "../definitions.json#/definitions/modified_on" - }, - "name": { - "description": "Name", - "example": "Jamie Curnow", - "type": "string", - "minLength": 2, - "maxLength": 100 - }, - "nickname": { - "description": "Nickname", - "example": "Jamie", - "type": "string", - "minLength": 2, - "maxLength": 50 - }, - "email": { - "$ref": "../definitions.json#/definitions/email" - }, - "avatar": { - "description": "Avatar", - "example": "http://somewhere.jpg", - "type": "string", - "minLength": 2, - "maxLength": 150, - "readOnly": true - }, - "roles": { - "description": "Roles", - "example": [ - "admin" - ], - "type": "array" - }, - "is_disabled": { - "description": "Is Disabled", - "example": false, - "type": "boolean" - } - }, - "links": [ - { - "title": "List", - "description": "Returns a list of Users", - "href": "/users", - "access": "private", - "method": "GET", - "rel": "self", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "array", - "items": { - "$ref": "#/properties" - } - } - }, - { - "title": "Create", - "description": "Creates a new User", - "href": "/users", - "access": "private", - "method": "POST", - "rel": "create", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "required": [ - "name", - "nickname", - "email" - ], - "properties": { - "name": { - "$ref": "#/definitions/name" - }, - "nickname": { - "$ref": "#/definitions/nickname" - }, - "email": { - "$ref": "#/definitions/email" - }, - "roles": { - "$ref": "#/definitions/roles" - }, - "is_disabled": { - "$ref": "#/definitions/is_disabled" - }, - "auth": { - "type": "object", - "description": "Auth Credentials", - "example": { - "type": "password", - "secret": "bigredhorsebanana" - } - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Update", - "description": "Updates a existing User", - "href": "/users/{definitions.identity.example}", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "properties": { - "name": { - "$ref": "#/definitions/name" - }, - "nickname": { - "$ref": "#/definitions/nickname" - }, - "email": { - "$ref": "#/definitions/email" - }, - "roles": { - "$ref": "#/definitions/roles" - }, - "is_disabled": { - "$ref": "#/definitions/is_disabled" - } - } - }, - "targetSchema": { - "properties": { - "$ref": "#/properties" - } - } - }, - { - "title": "Delete", - "description": "Deletes a existing User", - "href": "/users/{definitions.identity.example}", - "access": "private", - "method": "DELETE", - "rel": "delete", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Set Password", - "description": "Sets a password for an existing User", - "href": "/users/{definitions.identity.example}/auth", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "required": [ - "type", - "secret" - ], - "properties": { - "type": { - "type": "string", - "pattern": "^password$" - }, - "current": { - "type": "string", - "minLength": 1, - "maxLength": 99 - }, - "secret": { - "type": "string", - "minLength": 8, - "maxLength": 99 - } - } - }, - "targetSchema": { - "type": "boolean" - } - }, - { - "title": "Set Permissions", - "description": "Sets Permissions for a User", - "href": "/users/{definitions.identity.example}/permissions", - "access": "private", - "method": "PUT", - "rel": "update", - "http_header": { - "$ref": "../examples.json#/definitions/auth_header" - }, - "schema": { - "type": "object", - "properties": { - "visibility": { - "type": "string", - "pattern": "^(all|user)$" - }, - "access_lists": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - }, - "dead_hosts": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - }, - "proxy_hosts": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - }, - "redirection_hosts": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - }, - "streams": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - }, - "certificates": { - "type": "string", - "pattern": "^(hidden|view|manage)$" - } - } - }, - "targetSchema": { - "type": "boolean" - } - } - ], - "properties": { - "id": { - "$ref": "#/definitions/id" - }, - "created_on": { - "$ref": "#/definitions/created_on" - }, - "modified_on": { - "$ref": "#/definitions/modified_on" - }, - "name": { - "$ref": "#/definitions/name" - }, - "nickname": { - "$ref": "#/definitions/nickname" - }, - "email": { - "$ref": "#/definitions/email" - }, - "avatar": { - "$ref": "#/definitions/avatar" - }, - "roles": { - "$ref": "#/definitions/roles" - }, - "is_disabled": { - "$ref": "#/definitions/is_disabled" - } - } -} diff --git a/backend/schema/examples.json b/backend/schema/examples.json deleted file mode 100644 index 37bc6c4d3..000000000 --- a/backend/schema/examples.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "examples", - "type": "object", - "definitions": { - "name": { - "description": "Name", - "example": "John Smith", - "type": "string", - "minLength": 1, - "maxLength": 255 - }, - "auth_header": { - "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk", - "X-API-Version": "next" - }, - "token": { - "type": "string", - "description": "JWT", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk" - } - } -} diff --git a/backend/schema/index.json b/backend/schema/index.json deleted file mode 100644 index 28bea1b28..000000000 --- a/backend/schema/index.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "root", - "title": "NPMplus REST API", - "description": "This is the NPMplus REST API", - "version": "2.0.0", - "links": [ - { - "href": "http://npm.example.com/api", - "rel": "self" - } - ], - "properties": { - "tokens": { - "$ref": "endpoints/tokens.json" - }, - "users": { - "$ref": "endpoints/users.json" - }, - "proxy-hosts": { - "$ref": "endpoints/proxy-hosts.json" - }, - "redirection-hosts": { - "$ref": "endpoints/redirection-hosts.json" - }, - "dead-hosts": { - "$ref": "endpoints/dead-hosts.json" - }, - "streams": { - "$ref": "endpoints/streams.json" - }, - "certificates": { - "$ref": "endpoints/certificates.json" - }, - "access-lists": { - "$ref": "endpoints/access-lists.json" - }, - "settings": { - "$ref": "endpoints/settings.json" - } - } -} diff --git a/backend/setup.js b/backend/setup.js deleted file mode 100644 index 0569e3b99..000000000 --- a/backend/setup.js +++ /dev/null @@ -1,145 +0,0 @@ -const config = require('./lib/config'); -const logger = require('./logger').setup; -const certificateModel = require('./models/certificate'); -const userModel = require('./models/user'); -const userPermissionModel = require('./models/user_permission'); -const utils = require('./lib/utils'); -const authModel = require('./models/auth'); -const settingModel = require('./models/setting'); -const certbot = require('./lib/certbot'); - -/** - * Creates a default admin users if one doesn't already exist in the database - * - * @returns {Promise} - */ -const setupDefaultUser = () => { - return userModel - .query() - .select(userModel.raw('COUNT(`id`) as `count`')) - .where('is_deleted', 0) - .first() - .then((row) => { - if (!row.count) { - // Create a new user and set password - logger.info('Creating a new user: admin@example.com with password: iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi'); - - const data = { - is_deleted: 0, - email: 'admin@example.com', - name: 'Administrator', - nickname: 'Admin', - avatar: '', - roles: ['admin'], - }; - - return userModel - .query() - .insertAndFetch(data) - .then((user) => { - return authModel - .query() - .insert({ - user_id: user.id, - type: 'password', - secret: 'iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi', - meta: {}, - }) - .then(() => { - return userPermissionModel.query().insert({ - user_id: user.id, - visibility: 'all', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage', - }); - }); - }) - .then(() => { - logger.info('Initial admin setup completed'); - }); - } else if (config.debug()) { - logger.info('Admin user setup not required'); - } - }); -}; - -/** - * Creates default settings if they don't already exist in the database - * - * @returns {Promise} - */ -const setupDefaultSettings = () => { - return settingModel - .query() - .select(settingModel.raw('COUNT(`id`) as `count`')) - .where({ id: 'default-site' }) - .first() - .then((row) => { - if (!row.count) { - settingModel - .query() - .insert({ - id: 'default-site', - name: 'Default Site', - description: 'What to show when Nginx is hit with an unknown Host', - value: 'congratulations', - meta: {}, - }) - .then(() => { - logger.info('Default settings added'); - }); - } - if (config.debug()) { - logger.info('Default setting setup not required'); - } - }); -}; - -/** - * Installs all Certbot plugins which are required for an installed certificate - * - * @returns {Promise} - */ -const setupCertbotPlugins = () => { - return certificateModel - .query() - .where('is_deleted', 0) - .andWhere('provider', 'letsencrypt') - .then((certificates) => { - if (certificates && certificates.length) { - const plugins = []; - const promises = []; - - certificates.map(function (certificate) { - if (certificate.meta && certificate.meta.dns_challenge === true) { - if (plugins.indexOf(certificate.meta.dns_provider) === -1) { - plugins.push(certificate.meta.dns_provider); - } - - // Make sure credentials file exists - const credentials_loc = '/data/tls/certbot/credentials/credentials-' + certificate.id; - // Escape single quotes and backslashes - const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll("'", "\\'").replaceAll('\\', '\\\\'); - const credentials_cmd = "[ -f '" + credentials_loc + "' ] || { mkdir -p /data/tls/certbot/credentials 2> /dev/null; echo '" + escapedCredentials + "' > '" + credentials_loc + "' && chmod 600 '" + credentials_loc + "'; }"; - promises.push(utils.exec(credentials_cmd)); - } - }); - - return certbot.installPlugins(plugins).then(() => { - if (promises.length) { - return Promise.all(promises).then(() => { - logger.info('Added Certbot plugins ' + plugins.join(', ')); - }); - } - }); - } - }); -}; - -module.exports = function () { - return setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins); -}; diff --git a/backend/sqlite-vaccum.js b/backend/sqlite-vaccum.js deleted file mode 100755 index bbf7e42b9..000000000 --- a/backend/sqlite-vaccum.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const sqlite3 = require('sqlite3'); - -if (fs.existsSync(process.env.DB_SQLITE_FILE)) { - const db = new sqlite3.Database(process.env.DB_SQLITE_FILE, sqlite3.OPEN_READWRITE, (err) => { - if (err) { - console.error(err.message); - } else { - db.run('VACUUM; PRAGMA auto_vacuum = 1;', [], (err) => { - if (err) { - console.error(err.message); - } - db.close((err) => { - if (err) { - console.error(err.message); - } - }); - }); - } - }); -} diff --git a/backend/templates/_access.conf b/backend/templates/_access.conf deleted file mode 100644 index 1562e6edb..000000000 --- a/backend/templates/_access.conf +++ /dev/null @@ -1,25 +0,0 @@ -{% if access_list_id > 0 %} - {% if access_list.items.length > 0 %} - # Authorization - auth_basic "Authorization required"; - auth_basic_user_file /data/etc/access/{{ access_list_id }}; - - {% if access_list.pass_auth == 0 %} - proxy_set_header Authorization ""; - {% endif %} - - {% endif %} - - # Access Rules: {{ access_list.clients | size }} total - {% for client in access_list.clients %} - {{client | nginxAccessRule}} - {% endfor %} - deny all; - - # Access checks must... - {% if access_list.satisfy_any == 1 %} - satisfy any; - {% else %} - satisfy all; - {% endif %} -{% endif %} diff --git a/backend/templates/_brotli.conf b/backend/templates/_brotli.conf deleted file mode 100644 index 39ef12863..000000000 --- a/backend/templates/_brotli.conf +++ /dev/null @@ -1,4 +0,0 @@ -{% if http2_support == 1 or http2_support == true -%} - # Enable Brotli - include conf.d/include/brotli.conf; -{% endif %} diff --git a/backend/templates/_certificates.conf b/backend/templates/_certificates.conf deleted file mode 100644 index fff752d56..000000000 --- a/backend/templates/_certificates.conf +++ /dev/null @@ -1,15 +0,0 @@ -{% if certificate and certificate_id > 0 -%} -{% if certificate.provider == "letsencrypt" %} - # Certbot TLS - include conf.d/include/tls-ciphers.conf; - ssl_certificate /data/tls/certbot/live/npm-{{ certificate_id }}/fullchain.pem; - ssl_certificate_key /data/tls/certbot/live/npm-{{ certificate_id }}/privkey.pem; - ssl_trusted_certificate /data/tls/certbot/live/npm-{{ certificate_id }}/chain.pem; -{% else %} - # Custom SSL - include conf.d/include/tls-ciphers.conf; - ssl_certificate /data/tls/custom/npm-{{ certificate_id }}/fullchain.pem; - ssl_certificate_key /data/tls/custom/npm-{{ certificate_id }}/privkey.pem; - ssl_trusted_certificate /data/tls/custom/npm-{{ certificate_id }}/chain.pem; -{% endif %} -{% endif %} diff --git a/backend/templates/_forced_tls.conf b/backend/templates/_forced_tls.conf deleted file mode 100644 index 3b0226a2a..000000000 --- a/backend/templates/_forced_tls.conf +++ /dev/null @@ -1,6 +0,0 @@ -{% if certificate and certificate_id > 0 -%} -{% if ssl_forced == 1 or ssl_forced == true %} - # Force TLS - include conf.d/include/force-tls.conf; -{% endif %} -{% endif %} diff --git a/backend/templates/_header_comment.conf b/backend/templates/_header_comment.conf deleted file mode 100644 index 8f996d34f..000000000 --- a/backend/templates/_header_comment.conf +++ /dev/null @@ -1,3 +0,0 @@ -# ------------------------------------------------------------ -# {{ domain_names | join: ", " }} -# ------------------------------------------------------------ \ No newline at end of file diff --git a/backend/templates/_hsts.conf b/backend/templates/_hsts.conf deleted file mode 100644 index c0a743ed2..000000000 --- a/backend/templates/_hsts.conf +++ /dev/null @@ -1,17 +0,0 @@ -{% if certificate and certificate_id > 0 -%} -{% if ssl_forced == 1 or ssl_forced == true %} -{% if hsts_enabled == 1 or hsts_enabled == true %} - more_clear_headers "Expect-CT"; - include conf.d/include/hsts.conf; -{% endif %} -{% endif %} -{% endif %} - -{% unless certificate and certificate_id > 0 -%} -{% unless ssl_forced == 1 or ssl_forced == true %} -{% unless hsts_enabled == 1 or hsts_enabled == true %} - more_clear_headers "Expect-CT"; - more_clear_headers "Strict-Transport-Security"; -{% endunless %} -{% endunless %} -{% endunless %} diff --git a/backend/templates/_listen.conf b/backend/templates/_listen.conf deleted file mode 100644 index 8ad3be224..000000000 --- a/backend/templates/_listen.conf +++ /dev/null @@ -1,20 +0,0 @@ - listen unix:/run/nginx-{{ id }}.sock; - - listen 80; - listen [::]:80; - -{% if certificate and certificate_id > 0 %} - listen 443 ssl; - listen [::]:443 ssl; - - listen 443 quic; - listen [::]:443 quic; - -{% if hsts_subdomains == 1 or hsts_subdomains == true %} - more_set_headers 'Alt-Svc: h3=":443"; ma=86400'; -{% else %} - more_clear_headers "Alt-Svc"; - http3 off; -{% endif %} -{% endif %} - server_name {{ domain_names | join: " " }}; diff --git a/backend/templates/_location.conf b/backend/templates/_location.conf deleted file mode 100644 index 5ff7f054b..000000000 --- a/backend/templates/_location.conf +++ /dev/null @@ -1,17 +0,0 @@ - location {{ path }} { - set $forward_path "{{ forward_path }}"; - - {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %} - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - {% endif %} - - include conf.d/include/proxy-location.conf; - proxy_set_header X-Forwarded-Host $host{{ path }}; - if ($forward_path = "") { - rewrite ^{{ path }}(/.*)$ $1 break; - } - proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }}; - - {{ advanced_config }} - } diff --git a/backend/templates/dead_host.conf b/backend/templates/dead_host.conf deleted file mode 100644 index adbf0ec4a..000000000 --- a/backend/templates/dead_host.conf +++ /dev/null @@ -1,26 +0,0 @@ -{% include "_header_comment.conf" %} - -{% if enabled == 1 or enabled == true %} -server { -{% include "_listen.conf" %} -{% include "_certificates.conf" %} -{% include "_hsts.conf" %} -{% include "_forced_tls.conf" %} -{% include "_brotli.conf" %} - -{{ advanced_config }} - include conf.d/include/acme-challenge.conf; - include conf.d/include/block-exploits.conf; -{% if use_default_location == 1 or use_default_location == true %} - location / { - include conf.d/include/acme-challenge.conf; - root /html/404; - try_files $uri /index.html; - } -{% endif %} - - # Custom - include /data/nginx/custom/server_dead.conf; - -} -{% endif %} diff --git a/backend/templates/default.conf b/backend/templates/default.conf deleted file mode 100644 index 5dd753ff4..000000000 --- a/backend/templates/default.conf +++ /dev/null @@ -1,61 +0,0 @@ -# ------------------------------------------------------------ -# Default Site -# ------------------------------------------------------------ -server { - listen 80 default_server; - listen [::]:80 default_server; - - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - - listen 443 quic reuseport default_server; - listen [::]:443 quic reuseport default_server; - more_set_headers 'Alt-Svc: h3=":443"; ma=86400'; - - server_name _; - - include conf.d/include/brotli.conf; - include conf.d/include/force-tls.conf; - include conf.d/include/tls-ciphers.conf; - include conf.d/include/acme-challenge.conf; - include conf.d/include/block-exploits.conf; - - #ssl_certificate ; - #ssl_certificate_key ; - #ssl_trusted_certificate ; - -{%- if value == "404" %} - location / { - include conf.d/include/acme-challenge.conf; - root /html/404; - try_files $uri /index.html; - } -{%- endif %} - -{%- if value == "444" %} - return 444; -{%- endif %} - -{%- if value == "redirect" %} - location / { - include conf.d/include/acme-challenge.conf; - return 307 {{ meta.redirect }}; - } -{%- endif %} - -{%- if value == "congratulations" %} - location / { - include conf.d/include/acme-challenge.conf; - root /html/default; - try_files $uri /index.html; - } -{%- endif %} - -{%- if value == "html" %} - location / { - include conf.d/include/acme-challenge.conf; - root /data/etc/html; - try_files $uri /index.html; - } -{%- endif %} -} diff --git a/backend/templates/ip_ranges.conf b/backend/templates/ip_ranges.conf deleted file mode 100644 index 8ede2bd99..000000000 --- a/backend/templates/ip_ranges.conf +++ /dev/null @@ -1,3 +0,0 @@ -{% for range in ip_ranges %} -set_real_ip_from {{ range }}; -{% endfor %} \ No newline at end of file diff --git a/backend/templates/proxy_host.conf b/backend/templates/proxy_host.conf deleted file mode 100644 index a87eedbd8..000000000 --- a/backend/templates/proxy_host.conf +++ /dev/null @@ -1,58 +0,0 @@ -{% include "_header_comment.conf" %} - -{% if enabled == 1 or enabled == true %} -server { - set $forward_scheme {{ forward_scheme }}; - set $server "{{ forward_host }}"; - set $port {{ forward_port }}; - -{% include "_listen.conf" %} -{% include "_certificates.conf" %} -{% include "_hsts.conf" %} -{% include "_forced_tls.conf" %} -{% include "_brotli.conf" %} -{% include "_access.conf" %} - - {% if block_exploits == 1 or block_exploits == true %} - modsecurity on; - {% if caching_enabled == 1 or caching_enabled == true -%} - modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity-crs.conf; - {% else %} - modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity.conf; - {% endif %} - {% endif %} - - include conf.d/include/acme-challenge.conf; - include conf.d/include/block-exploits.conf; - - {% if access_list_id > 0 %} - {% if access_list.items.length > 0 %} - {{ access_list.passauth }} - {% endif %} - {% endif %} - -{{ advanced_config }} - -{% if use_default_location == 1 or use_default_location == true %} - location / { - include conf.d/include/acme-challenge.conf; - - {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %} - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - {% endif %} - - # Proxy! - include conf.d/include/proxy.conf; - - # custom locations - {{ locations }} - } -{% endif %} - -{{ locations }} - - # Custom - include /data/nginx/custom/server_proxy.conf; -} -{% endif %} diff --git a/backend/templates/redirection_host.conf b/backend/templates/redirection_host.conf deleted file mode 100644 index ec022134e..000000000 --- a/backend/templates/redirection_host.conf +++ /dev/null @@ -1,28 +0,0 @@ -{% include "_header_comment.conf" %} - -{% if enabled == 1 or enabled == true %} -server { -{% include "_listen.conf" %} -{% include "_certificates.conf" %} -{% include "_hsts.conf" %} -{% include "_forced_tls.conf" %} -{% include "_brotli.conf" %} - -{{ advanced_config }} - include conf.d/include/acme-challenge.conf; - include conf.d/include/block-exploits.conf; -{% if use_default_location == 1 or use_default_location == true %} - location / { - include conf.d/include/acme-challenge.conf; - {% if preserve_path == 1 or preserve_path == true %} - return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}$request_uri; - {% else %} - return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}; - {% endif %} - } -{% endif %} - - # Custom - include /data/nginx/custom/server_redirect.conf; -} -{% endif %} diff --git a/backend/templates/stream.conf b/backend/templates/stream.conf deleted file mode 100644 index d7740ad10..000000000 --- a/backend/templates/stream.conf +++ /dev/null @@ -1,29 +0,0 @@ -# ------------------------------------------------------------ -# {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }} -# ------------------------------------------------------------ - -{% if enabled == 1 or enabled == true %} -{% if tcp_forwarding == 1 or tcp_forwarding == true -%} -server { - listen {{ incoming_port }}; - listen [::]:{{ incoming_port }}; - - proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; - - # Custom - include /data/nginx/custom/server_stream.conf; - include /data/nginx/custom/server_stream_tcp.conf; -} -{% endif %} -{% if udp_forwarding == 1 or udp_forwarding == true %} -server { - listen {{ incoming_port }} udp; - listen [::]:{{ incoming_port }} udp; - proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; - - # Custom - include /data/nginx/custom/server_stream.conf; - include /data/nginx/custom/server_stream_udp.conf; -} -{% endif %} -{% endif %} diff --git a/darkmode.css b/darkmode.css deleted file mode 100644 index 4215a75eb..000000000 --- a/darkmode.css +++ /dev/null @@ -1,247 +0,0 @@ -body { - color: rgb(181, 175, 166) !important; - background-color: rgb(28, 30, 31) !important; -} --webkit-scrollbar { - background-color: #202324 !important; - color: #aba499 !important; -} --webkit-scrollbar { - background-color: #202324 !important; - color: #aba499 !important; -} --webkit-scrollbar-thumb { - background-color: #454a4d !important; -} -.avatar { - background-color: rgb(48, 52, 54) !important; - color: rgb(161, 152, 140) !important; -} -pre { - color: rgb(195, 190, 182) !important; - background-color: rgb(27, 29, 30) !important; - text-shadow: rgb(24, 26, 27) 0px 1px !important; -} -.close { - color: rgb(232, 230, 227) !important; - text-shadow: rgb(24, 26, 27) 0px 1px 0px !important; -} -.form-fieldset { - background-color: rgb(27, 30, 31) !important; - border-color: rgb(53, 58, 60) !important; -} -.modal-content { - background-color: rgb(24, 26, 27) !important; - border-color: rgba(140, 130, 115, 0.2) !important; -} -.modal-header { - border-bottom-color: rgb(53, 58, 60) !important; -} -.modal-footer { - border-top-color: rgb(53, 58, 60) !important; -} -.alert-secondary { - color: rgb(185, 179, 170) !important; - background-color: rgb(37, 40, 41) !important; - border-color: rgb(57, 62, 64) !important; -} -.nav-tabs { - color: rgb(174, 167, 156) !important; - border-bottom-color: rgb(56, 61, 63) !important; -} -.nav-tabs .nav-link.active, -.nav-tabs .nav-item.show .nav-link { - color: rgb(181, 175, 166) !important; - background-color: rgb(28, 30, 31) !important; - border-color: rgb(56, 61, 63) rgb(56, 61, 63) rgb(30, 46, 76) !important; -} -.nav-tabs .nav-link.active { - border-color: rgb(35, 77, 136) !important; - color: rgb(85, 151, 211) !important; - background-color: transparent !important; -} -.selectize-input.focus { - border-color: rgb(35, 77, 136) !important; - box-shadow: rgba(39, 86, 151, 0.25) 0px 0px 0px 2px !important; -} -.selectgroup-input:checked + .selectgroup-button { - border-color: rgb(35, 77, 136) !important; - color: rgb(85, 151, 211) !important; - background-color: rgb(30, 33, 34) !important; -} -.selectize-input, -.selectize-control.single .selectize-input.input-active { - background-color: rgb(24, 26, 27) !important; -} - -.selectize-dropdown, -.selectize-input, -.selectize-input input { - color: rgb(181, 175, 166) !important; -} -.selectize-input { - border-color: rgba(124, 115, 101, 0.12) !important; -} -.selectize-input, -.selectize-control.single .selectize-input.input-active { - background-color: rgb(24, 26, 27) !important; -} -.selectize-control.multi .selectize-input div { - background-color: rgb(35, 38, 39) !important; - color: rgb(181, 175, 166) !important; - border-color: rgba(124, 115, 101, 0.12) !important; -} -.selectize-dropdown, -.selectize-input, -.selectize-input input { - color: #495057 !important; - -webkit-font-smoothing: inherit !important; -} -.card { - background-color: rgb(24, 26, 27) !important; - border-color: rgba(124, 115, 101, 0.12) !important; -} -.tag { - color: rgb(155, 146, 133) !important; - background-color: rgb(35, 38, 39) !important; -} -.header { - background-color: rgb(24, 26, 27) !important; - border-bottom-color: rgba(124, 115, 101, 0.12) !important; -} -.navbar-light .navbar-brand { - color: rgba(232, 230, 227, 0.9) !important; -} -.nav-tabs { - color: rgb(174, 167, 156) !important; -} -.table th, -.text-wrap table th, -.table td, -.text-wrap table td { - border-top-color: rgb(56, 61, 63) !important; -} -.form-control { - color: rgb(181, 175, 166) !important; - background-color: rgb(24, 26, 27) !important; - border-color: rgba(124, 115, 101, 0.12) !important; -} -.footer { - background-color: rgb(24, 26, 27) !important; - border-top-color: rgba(124, 115, 101, 0.12) !important; - color: rgb(174, 167, 156) !important; -} -.text-default { - color: rgb(181, 175, 166) !important; -} -.text-yellow { - color: rgb(242, 202, 39) !important; -} -::selection { - background-color: #004daa !important; - color: #e8e6e3 !important; -} -.selection { - background-color: #004daa !important; - color: #e8e6e3 !important; -} -.dropdown-menu { - color: rgb(181, 175, 166) !important; - background-color: rgb(24, 26, 27) !important; - border-color: rgba(124, 115, 101, 0.12) !important; - box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px !important; -} -.dropdown-menu-arrow::before { - border-right-color: transparent !important; - border-left-color: transparent !important; - border-bottom-color: rgba(84, 91, 95, 0.2) !important; -} -.dropdown-menu-arrow::after { - border-right-color: transparent !important; - border-bottom-color: rgb(48, 52, 54) !important; - border-left-color: transparent !important; -} -.dropdown-divider { - border-top-color: rgb(53, 58, 60) !important; -} -.dropdown-menu-arrowafter { - border-right-color: transparent !important; - border-bottom-color: rgb(48, 52, 54) !important; - border-left-color: transparent !important; -} -.dropdown-item { - color: rgb(155, 146, 133) !important; -} -.btn-secondary { - color: rgb(181, 175, 166) !important; - background-color: rgb(24, 26, 27) !important; - border-color: rgba(124, 115, 101, 0.12) !important; - box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 1px 0px !important; - border-color: rgb(62 0 118 / 90%) !important; -} -.btn-teal { - color: rgb(232, 230, 227) !important; - background-color: rgb(34, 162, 149) !important; - border-color: rgb(32, 150, 137) !important; -} -.stamp { - color: rgb(232, 230, 227) !important; -} -.bg-yellow { - background-color: rgb(144, 117, 8) !important; -} -.bg-blue { - background-color: rgb(39, 86, 151) !important; -} -.bg-green { - background-color: rgb(75, 149, 0) !important; -} -.bg-red { - background-color: rgb(164, 26, 25) !important; -} -.custom-switch-indicator { - background-color: rgb(35, 38, 39) !important; - border-color: rgba(124, 115, 101, 0.12) !important; -} -.custom-switch-input:checked ~ .custom-switch-description { - color: rgb(181, 175, 166) !important; -} -.custom-switch-input:checked ~ .custom-switch-indicator { - background-color: rgb(34, 162, 149) !important; -} -.bg-success { - background-color: rgb(75, 149, 0) !important; -} -.btn-success { - color: rgb(232, 230, 227) !important; - background-color: rgb(75, 149, 0) !important; - border-color: rgb(101, 199, 0) !important; -} -.selectize-input.full { - background-color: rgb(24, 26, 27) !important; -} -.selectize-input, -.selectize-control.single .selectize-input.input-active { - background-color: rgb(24, 26, 27) !important; -} -.selectize-dropdown, -.selectize-input, -.selectize-input input { - color: rgb(181, 175, 166) !important; -} -.selectize-input { - border-color: rgba(124, 115, 101, 0.12) !important; -} -.selectize-dropdown { - color: rgb(202, 197, 190) !important; - background-color: rgb(24, 26, 27) !important; - border-right-color: rgb(61, 66, 69) !important; - border-bottom-color: rgb(61, 66, 69) !important; - border-left-color: rgb(61, 66, 69) !important; - box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px !important; -} -.input-group-text { - color: rgb(181, 175, 166) !important; - background-color: rgb(26, 28, 29) !important; - border-color: rgba(124, 115, 101, 0.12) !important; -} diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index c8f4b4f98..000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -dist -node_modules -webpack_stats.html -yarn-error.log diff --git a/frontend/app-images/default-avatar.jpg b/frontend/app-images/default-avatar.jpg deleted file mode 100644 index 798871673d0dc4721f00dbf5afb8e55b574b521e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1753 zcmb_dc~H|=5Z+&YAprt`C~}4S3@Az?2INpcOducz25jXhB2Yo1CLDrjYa&7wHG~93 zQ87Y6z@s7s0j(lnr5ws3mv~S_R1l3sMG>1u?Kt)y|7g4W-psz;otafJm%gZk!Bx2jP6UcuZAa4MY@_RfeVhR`%f|3w<6Oa`N4Ei4GCsV?qSUd)i z-vZ^2`ElN-+ULQ#$k&SFy^7YD)LlQ*d4R-JzQfc80}$*aNLV=!$W!+uO8WBv;T{2! z0}t?wHgJ_~2Uz%G4J1z3gWj|~EPizmG=8=22U+_JK-ee`8*BiG`8Lo>l>nwE3fyl3 zVEWb~5oLa^yvaLL3S3@)0PM!`tk-I^hlyW z`^^ww=eUCK5)V|hQZbLI0H$SY^ro#PfIKXS=M|ji0j3Br_niQgojHU*r2r1G;7n%$ zM2$8o7}EL&IUfl4Kn+Iob29#A{Dt}G|1_Vb2?&Z{@i-+^=_5)6gJMabWa=@?%>FC) z$eh{KfIu!^%f$Rjr{~%PoF*r+;_u5xmVz8~z<+3El~_CQJR@n^Vj#eB-}9p{ZD=er z$qu*WPBke>hv;SgS+1j!ts#r9lFD_~kJS#j@haKpItt0m#ekm*UE%Dos-Gn_F@77T zV`0swC>0yeACuv_=hp_TXC@8GN=nj?Y3w^Qn5*w**VEGDTOJTp z(`+fnvko^;f3ssQzrJH%lacm=3mz)DW7~U=)xKpvJ6vOD?Og<2WK zLw|Sg>Jg2*^_C_;m7u1TW__m?WaK8{yTgOBui?3L^<@*@y_~*54motpIbw&me)~94 zHN%UTg!Z;o>&N{Poy&8}?1?2X#Uj2@Q(&vdGJ$zZF){Oqnpe85)8d+(n-kBpc3u~^ zN58zr&+?3+FCkjIaM{|F?(8F7ZD>EV-UA!W?%o}`;nYSJzVu8FQ$>lv!AmOlbaSu1 zIZ$*{`n83iSC>7N@9g@Bi;v&htk#fd7&FEnznoq-dQf!5Iv|NztCn4rp-#)Ycw@J? zP#omQB6^Lc`idqADp`{ihF4RwH3BLxEih?O+Z_B-i&FH!=WT)3EE_>Go{Q_-FsY4F zgD_I0v1XEnD~j9_q5R!xhN*9RXahT_cvdk*y z5mALG!D#1MlZ?#JA*yjB!1eP?xpIzC_fwg)paf|F>VMOomNxZJSw z?ke{X3!0gO=B@98FN%l#d&B6KRgz)Hjja0gVfB(LX|wYQPYcE-&3PFr1Dmks7pC_M zjm7&9MwUJ)F6s6XR6TdSe&-cg*pkm(FRb;iV{o0en>WgVRWZ;K*OJnCdM5L?p-rCpeay{*WE5$G&tWk}0N}R@t&hB67$ELn%uC(cl%o*Z2bDkFYyU3wlP>qhf*BH|0 Z0{>nGgda5wQM_bG`|N&vAq%I0`Z* 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 G5}E)a52a54 diff --git a/frontend/app-images/favicons/android-chrome-512x512.png b/frontend/app-images/favicons/android-chrome-512x512.png deleted file mode 100644 index 6c69a48d882b60b0cdda80494a9e72131ada5919..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54834 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

VQwFh`Hjc6(Uf`bK=j6fYo` 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 diff --git a/frontend/app-images/favicons/apple-touch-icon.png b/frontend/app-images/favicons/apple-touch-icon.png deleted file mode 100644 index 111caf46fb1509f7fe219695462341f05bb7de01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15800 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஘>Bvz;i8Zhb1I~~U_0aoN? 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 - - - - - #333333 - - - diff --git a/frontend/app-images/favicons/favicon-16x16.png b/frontend/app-images/favicons/favicon-16x16.png deleted file mode 100644 index a456ed4f6e3f1a125e8d61709f9449c26eb3d559..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1220 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 Ef?^=LrvLx| diff --git a/frontend/app-images/favicons/favicon.ico b/frontend/app-images/favicons/favicon.ico deleted file mode 100644 index 9eae8b5f6c2c0feeb72b7aa99dd0fdedb840eab4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI32UwKXw)eq?ekbRc+%yZxpaL@tFcpSjK#Bzg>>3L;EU};`R*DrY2r9kzCWuC3 zk1@uAqQ*wGD+&mT)S00O48yzc{~Z_vj|P*Idv5OYeDgei_W<+G`mJ5p-fOF&p{dbR zgU8cAsiSc~PeVhbp`oFp)Bf|v8XC{=yN^-Qe_z&1Lt_^{z`|$nh=wyn|6cXdZ7cr& zzaLfu0fNIle7JEvKJ3$ftxtsta)$bIGkn+SdV4Qpe5do*qI*A02>;ea@V9`U1aPmA)x)-t1|V8>OZoEK$QQ;YI&gK=@anZ zhB8C>*_r;_%eQ@b@lQj=QDBdyAQB;oE{(Gf@l2W=p#7h23v;owKeSLRmgbK(m$vR4 zQS&ljWX(bFFYzBKH3_h-7y$NxKV6;pWU{O2=+#b-b zc6o%Fo^lG8L<-S&`tKbY+$-EXik&b%$Q#_HkRoYP(CN5wej|1;SNHz2{SZ!_b}ZXV z^Uv@@u&!>9z@>JJ&`JIr`~)g9`9{7(B9!*gWt?T}>70*)s!jB+5EtX8#En@(*6Tf2 zX)~*U1oE;enxCGJLijgH)qYXlMStQElYHRw!lN8u#A80*}ot8Lr&DZlj2746EzX{sIc5Mx0d)$O( zL4W&DKMcd4^+25u7p$p24)!Lf75v%YzeKY`S|+pWwiu5nsMBMdQ?aXkp!wap^4EdC zuC4yj2E5xO%9`ds(ZPRU@ktUiIzYC8wfqLy>)ZLC?q$3F3T_3|1RaGeg2kJXtwt|5D8MIEYx{e{7U;CMGf~*Jl*FsJF$rS``KM%J{*D)&@OslUktTEL^xAMIHL;TLpG()xI^?9cPL|>yZN(*Jzcz2zqG&FRxsz* zC*kJV%VWmo-HUb1ry!Cr`8pLl_5c+ji3M9M2@^*XKa;&1opmNL77hI-2>Q`^$W+@< z(=DRt5j)CbMtc`PgVo>pz7qTl=s8r)a=*$!KiUU&x-E!cZQTyxOt~Z2TU7i`a>Acd zO)>TE6Y#%)cIYzheEq)fjvbdj1nj=hs~CqI5@DH!wJb)(Px}xhNu0l6$dnaeC$tt0 zop#=T_yz-T7q{~ddf#`ef8W9HmRZemyF&9vw)0cqckjZFHUPhz#e9&Y|5d&K{N46X zu=j{{yd#RS&q|B1Os(t8A1O(o`4@n_rJes+!0=7X-^K5iN!Ncddwbq%J>b!z%P2hG1E(r*N*EYHx!J!Wpu0hN40(wf`Sl#*DezH(Zi13H5#%YN6{N3f{`u z$bb)vQ1`y@ftffVV0_$IUnzWe0(D#e2L8DWqtKEfPSR__O;yx!(-bM(6om+NKLS4d z2sI17t0Th2wIRZ3^7qEWh>iM>5!X0-SXR#sv8j1)Yyjr}f}+~_X+JU|CGivE9YY7E zP7mpoG&yKr(xgC=FwXBW{OGp-8y+du;p~51#7%9X`CA~ujccwHxYoT#vC$b@bE#shRZY`g>zb2$tZQr_>~8<{1?H9Q{B2`F68k#dA@pp@^ng#m zzYHQ!LBe?dZeu;eA4+sMX${5PeF_=aJy8Ewd2!7I{AzAh$zNUf?b{^g=2A9dqiFC; zAp(BlX;nkOPrzOch56akFyDR-bI{xT1obaZo*F0uw<%Qjq5P)z>lh|inxFHJC?3Oy z&%wWpo2(#w@7EOizlvSrUQb4OHFV(@%#rsOIm#}Ihf4{~PRmj(r50J%P&?aw@Ug5S zo`SM7ZhFtUtf!s^@hklVaUJsz-4-z;BPjSVCwXenfVlB~g+aF49{Y$q2EOh4+do@W zDH#?O%2M-U<*()i%6@1APgNhZ;&*_+5TCzj>MRx>*C&kGo&7P0df2qY|2_*-?#j8@#js(Cf0v zR5^t`LP!6h`NsZ2G`~tS z-3b5Z@r5Sg@~Cb({ll_&M+c>cUTtL-4b} z9s$+RKA23GlTPzdHO4^*q(^_Y^9$T7X?C?-2Bne6Nk?UeT~3)`e_)or=wv<0g0y)* zbUXiB7CfW*i@;8xMXY<|Q}#6~pOgNEzr7vaxqgAWH)IQ)1b-9wo6vtwK-SPZ*RSPo z@vj(^O2Wn+x&4#d>Hgq$f#`isLE8Ky-SXQMK4J*(*A%gHTL~+N?AOjAy?A-8L4q?_ zk301t_z(iUH+~SzmjB)K8~JQ=TiI-L6X|>7htb+6KASo4``B;4OzWusb&MZKko8YK z`Kbqcr7u2l7<*-sGX2owc*oRk!{B3AA?3tDN63OfhbT|Iv)FUqs0iU@ z3u8uIF-)9%@@1@D3f6+S$6?}_ZsWHRMfVv{5&R(5rDepTqIHW!xw045gE*8#NVfyl zvUVQ`H=(&!;8CA~7?s}VbQ?eG>tyuQ)5$P1(7BjCO8aCT*atQJWHWU0t;4cm}`Q7|FsQ=Rq`H zA1Lq>L%kc6?XXPBLHjuB|0r=Be1gm$k|E#C$(j*oa-BZ6O9>Tbk#LI(RD?wm-3E01 zci;aD?+&&vd3JZ&t4cDfQck8;){)WWO%1}Qt&6_8MtU3ECYuZ|QH{pmHxhPu8O8K_ zLYTf!h@s~TioZ^-_Co>wU(lxAR*$as zm{B3M0Q(W>wG(`pTvkVn^QkgcHW|mdL$nO;kWH);(mMS;k4R^6HdeHP`%Rp^s;*u* zM_v#9wl&SEmY7bIwIa^$SewOS8JWQ;lez1axO(W9^+9Ym#ZAAsBG~30iFL@M`CrG_ z=l>8hHb)XJj_P)NseJ7^nXe?J8OtwooX!8{pbOFaRIY4>OTuo zow=AyL2T5vN2mmQB&26%F6;Xpvt%XWq6+X6@E7`7KJZ8wpVRGm&xiP6TX;{Cliy%y znA;LtaKMQx%oCPq)E!(R39x^RmXx4cGx$M7dqDt z1HUh%fNIeO{@~_YaB6y@G=fsm2F>5HU(|p<9AdB2E%{TDci)5iAJ76Qk9~t`Ks`<_St(MD{y6|re)`+7{r7ByHO#t8mdYYqMz zP(H@Qc;2)!bIhT1{j1~a5;I%bR*9*!+d2Jx_Wz9E8(wPFEDN^Bp>VjL#xVYIXCFVn|Al>0yVIih-LXuK&Y zPeSOQmDX*gtbbdX(X*}e(o~fcqsf#0T+`OA47x1N=;=e3ziMcd(B(gH_Xwqu-mzM1 zXf!oxdTB89fG!7ED^+C^A%DluO$!L|!RMNKDy=WkfA1;luPS#G zX>}=k7HMUu{{E)y^7kF;=i0ufX&WKi=PG~n;)wQ?U$yP%CF+gOqHKaTbHQ(rnGfb^+0xk9G@eZnQf)I-qiJ|u2;II>Cd&5 z^gS&d$Oh`Tr`56Zz%T!gzrqD~`}uLsPgt+(Gw$23m-hWnxwDt`YmX4#O%e8x=>go! z4XcKF*5e*d+A_u(6X!uoKX@PTM|iiHSL*B;UcNg>{HP*Ckd1o;*U4s9EctHeMzX`BTg%o**~v|D7sSNh?1@7t|CSTl z!70q-qJ4xUMi?fF5k=dEyTNu2bS}=(&we|5_3vKO`MwQ35Zlvn@jk@#W#*Q$y?oA7 zBjn@fEEGOIwaxU^%gyYfW*_6bR4_k_3K9I;9ANZ4+$nww&{^Mlq4#D&_3%XbKt)Rx13m2h_Le6~1W4BMx1mqB4R^eS1cSERa! z;jUlUagQNXc$1{eI8AUarofrr$uP$-bL9R5a6U}D4c5L4VtZ^u&iprHO0H|kB*B{6 zJTMd?pBaIi>;#iJbz`~qB?bDrmvEQj9N`bSN-ftdr0uuj9z`zhf#jhdw1%4AhW%eC z*eClJ93y_ieM>j8TO5!JElcZ_C5S*(~ zq2fs5%isC)O1-(0kuTk_L4WDw2#MEC8C|u@CmW zAysT{X!1;oe01zkM!vDez$+c?nLPKtX59FEZmi?ogy_+iaF?eoXBA-{7mR#u7@c#5 zykqTlr6E_l?_KuwT~2GVHe5S^{PH#aw)*kdrzM~t%n>fAjmI8GiMBXnY*RXVNZ((3 ztDj%NEY)1C_hvsCKlw$Us1c{f!v0x|#a5IDSLiR8zF^4Ir0)!!9g%1KCC1J>Mg4r| zQrq84(#)47yXz-$+=6%6w{OcO()K6vE9m*484O+QuhdQ? z!J>|_FlU2-%O2!9-)Y~8+}9Kpw;bd>p<;~AC?{}^v?%C0pmS38@l9|1cao2U|8hEq zgLWV|vm?XJYREKhxzvUArv8z8idr^cA8{X=OoAKh^D0--g+(w#h z@grjSsv%oaMHxxTl&1x!RQ6T%z~Av&iu1Qv%uB{lhh1%rm>zmxo#!Eck;wo0tc=cC z*yHScCgVrq$Gv$XjGw~^;a|-UGfSq=%in2V^!Qgzob9JMlopdQ6|d>@eQ!}=tC(|E z`Gn5V8OPTWBkyvGzg|YqNyX4+Xb{%N!TgEko(7!TC9GlBh>)F6jUIN1jMx5=%+tC2 zXopd@X^1d;Z@A<(iL^R_bNi$Owa;&tlU}}TuRe#n|U$M~yr`?rpoBe}?VBv#VrVM-?`f!hRKU#m>z1S7tcNJq0zhe4Y{H161=e z>=(iQQ`o1lJ`u5vYEEdzcsMbcUv1*9`+O1Rg_>Pvk4s}6a<_uvH{%aAM*w-LY5u5@ zi?NUdhBDNFKkH1bk^Y{Cvkb#4kMZ`tEuH`jedwbQ!$m>W4v@{?|pxV?T?}5#>ZOjVjNaN zXL+GbBjEor!P@#W{P|TAgfpcTU}!X%EV~2y%V7WCj=$gL!FeD9XK4uc73I)1Zgk^( z_SD?jOuK6rjK<$D70j&h7S62w)At{pIXDLuJz(1uF~R{(Y<&f5>g_U|gQe+@&J^m6 zy!b!5|LV-a`+@Z3gvrMl%*k`m7oY##dE(CB^}qSP?Ysd0(#Hw((L#|HA&Jd|;(g@V zl+lAB_tNNJ)-$85RI5nq1*0kbxwUd>k)d%$JO&rO&mB#uWF=@R56I z1}ZapT2p*KPm(Z;kmq9vkq5TfYBx-uQ_vk%2kJPd_#K&spUsHoZsVWy*Xi#1m-`#| z%HMpsS;;;RloWm!z`L5~rstRIWxQ);tWC`4zxfdUh`V6@M3yqgS;&+4*;IrC3h(aq z$t~$J9lkIETH~Avo)lLV0FC+ccw#yV=8vDb+5+EGkAD^tHD!Q z5HCv_GxF@c{mzG@_gHL+^<;mS7--;OvgfNc9evu+_09<1+E<5!%j-Aur&R1`;r|iX z49Txqt5*~m#LtO}9eZ7x^v$u>J)&LtZu(17{J8Gvk(S%6)%J0RdzR7Gf+XC%4HeF- zy~4FGKc)9o{%|whr@i)$x!)^p+#Rb(>qDpU|5>gt)14dSBXNz08{<1o{X8eBdKNeN z^-auuH*j}7L%5{&Q`Ex{{9lvcjaD^*e#2!tVP=t^A@;r!ZW(zpVM4&3gbDsL)z6C` zw@gKR9UusAsvo=i^~FW8jeSif%EBbb`^`10CL4`k20Ql;@3)y5e+l>HWC0RypPj;W zlRBO+Q%)7!R{HYO6!NhN%@@CoZ|sXXFVxgtMr=jZWFh-y@Y4Sm(a&X2@+BW$qAbWd z(AV31OULv4dv#7mc`c#b6D<#MXYM91z2#G^TROOx6^NWsppB?jXZnBxpDq4!|7|zD zgZESC91Gny`)~)p&9ldIhZn!fG<%?o({}ddh)AoWS0b$voYegGB~NW9ys9p;DpG_S9dG)P9r%*sx>wa;9gW7^8O@bE zt+6zEK+PC>{gjX3USy=r)uKqNLmln6M6T@NR9R~(de9QVy56!|KcX<&ct<7f71aNN z_Pk&^QC?zhE{mCET)um=@w3Qi`@5!z)2cf9kD3Gd&VE?e9StwG(sQ{le`|vmf2lM~ zFjJa=eZ_8DlWGU#UHhp&|Gv`rnDTS({+1P-sD=cCX}Ph+6APA`PHX6P{B-_4lVYN2 z98|+Hn3vD|S|i}SYczefNqOQNm7$?ggxNN?r-nvmriR9~MH(7chP8=)Pya5XXWM7! K&(hD(&;B<*Tkw_u diff --git a/frontend/app-images/favicons/mstile-150x150.png b/frontend/app-images/favicons/mstile-150x150.png deleted file mode 100644 index 3586691f014b4fb1f42c6fb688309168b2224f43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11672 zcmd6N^+QvC)c;0HryvbVcaLsq5Rh(AKypY+4p0;%1PMv$l2T%9!~g*S>Fxp2vC$3R zeZJ2h@jO55{;+%R`+e>??-Q@rIiFtWYmgAq69NDL5-m+NBLDzM`rnTL5A#n@{Z$D7 zAYiAZru;H!{$MF2)Y?Dy;b>+n=UcySidNR?K(%_ZjstL^E9GMi6ut~JR2C@gV*g>J z7De#mJ{*3iZW>O6!;5s|spB)Jp&=rCYph%!DPF=_T|%wBlO~zF$muJ)6&fCqlj5RgvK9wCF;AGa}nCHa&i^kVgFgAm(J<`&ZTwdHz}l|nCx%zz5jhD!^3_^ ztnI~ZRe;)O+}I~3M~Um4tT`JrE$H{L@h!^UN(dxRz7y=*9VVGBpsR%Oa$K3BC9d?))bhl1B4XnNVNmpJe&BW95(*YXo1uLz?!bUuO}Qu z=%Np5`=w5$s1DuxJvN4iI;3!j*u$3&8Sd>bClaWf*pd4pxdt+G0&6nAXT!Gi#J9+! z3l5}!W~#&E$kY@3C@?U~IJS-c<{#VyBZk0)F_m7@PU>Qe=Dve%l2;lZ3S zA%fq-x^A{tkstIoPcUe=JgBZeENQb?Pvz>Q!d|UwgVRk{%mmO_k zFDlGp{>=+D%B_?_h|+-#cj)SJI7@Ik?h=UVt~_H&h_EKRZTIPP{GU%<1of`%s6-Xr zo*m|VpSTkT>Y3cm-y!-2EuZipRb!&d1eAz_fY2vUv881ZIzgLXE;fgVDHCGnR}HL+ zQF#OOr}0Y%lV`AQ0hXQBe|(U2*`Q$!eI0#Q0cK`rYpkN(CD(g!XR z=`wJF-5>@46kd4pn>Tk}6IAi0b6g&s4!sKEoK(X+SM10YKVRnA>98DRKdHX5 zoB`lc8@fBd!VaR|cwEL7G<&z057!78USsO(DVo-1s*@qi5VWTmmFueY1WdoYfickX zpBHVxMSPDx1!AGN_}N3u)TN$BK7F*Osqp2^n>Nx%%x;#aua*_UFfNmtXC6{{bDl>c z5MJLgWODanvbFXfwgI`me}{oHKZ=Qizn4PaHG~Vy4(9KoL^2`_ngd%G7bH)N4dXXY zVk8&=7Rcr8E?c=`MhJj=a~1!Q7#NyLOU#n{+KgY2CxIP|F(YwkAhk9>K11zDq$%$=7Gu2(ku9C zgL{SviAcqwB=`*6y!g-a(@wR0*B}uw#CM^1UESE>k8bhn)SUTO4znYA9%Mrcc~LqX zxdwW!x;bAz_6IRtHiS*0C~x(m&f@{O2rMM0eUH+6SMP~IC^NWXmXBI%XDZS|f-Wjn(JiKJ0%USJaNfZmrSujzI-Dp%q$ zqqoIUwu|h?R@K%nGox&?F#gSriK5VFBY+I-q(7fGl-2DgvC1g)BNPjcyG*|;d<;&o zN+D$)=FW$a-9CacuP!4^c*u)52tOx%79JJ_b5=N8+lkf4{tey$8emD0KMvm*h=9JG zZ(hn$+p75fpaps%ld^oTm5Lgcl2~DFfB5TjVQ0V2f)S);6!g)x5{9!3^slY{ee~gu z-2`cdn0eKE-0>3U1E3FR_Pp~8{SIo1+S>be`m5E3OP*?r&@6lQ(N&Q6bbYs&mh4hW zK+BQgy^12*7rBdZ$h%;8&Ada#5G!L$*odaVX!8T`>+bGMfeZ_D19~G=cfi08TBd}I z0ixsA8HqmPFh9kKC0hhq^N6K;Gg8u-qGyaGq6jPfiHj#m+p8o(FL zX(CqDkybJO4DaL6ne^}b`aXnI8-gR<0CGB@pqpRohaHF}?>gix7xq(%u+%1CgpP$k zH$kT%Ry+Q!-M9Ky_dgPMTRZq2Fz(B(MN@hAudnaU{~-a}Gpyk{5hL|xOqUr9QYJBd zuaK5qyXG!2NV>~1EQj~|{s^bOr3i@LN;76{EL8&U=uEy*+CxX_9CX+9kq=*EKL~ykguS|cgCHY7dF)f! zS;vKvpT@eA!ae>#dneOoJb>+lw}k^+ZtSgkw>n679mUhI>~`=BuG zFv-Y6xic~QEuKl?%2n21^Y%tL@xtkWi;tTX#HP|OQ`Kr~ZRS|+%={-z)B<*`6!`DS z3+wY1rWpg~Dri>gztoW*)aM4xLw0UDf(}(z9{%Z+4OjgV8;^2IYTkZ-G3`$yQ!9ki zV@2SdAX8)~Q(5y5J4Nv0EyZOeX0M7hh|BY#a~{CFP!_SHGCwD*h|dH#L#y}L(dhML z5CTP^pn=$e^gY6VqY+MvbvLIZ<-eI5g+d9JsUgh9^rWM_gcznu`xhcnHPZV(C-}OY zB*3582jjfrjQU9?S5_ASa4oN~+wSI=QFgWRtG)EHVxFI!lA1iuRub`<1AYfCt&d47Yc*>?3-Xsc>NHODWmpdhbsdk@qxChS1Z(M5Xoc$_`G#(U4y$0QKJ#%8uB z;H#B)_qFZ*mI|hdcIup(i-u*xJrzC`{7zKgi?`Dqzh@Idj0sMG@5Kbo#mh#9^@$SY zr9oMbXm@fSk^5R#Cr!Qa-lH576`e5B3pA;%pv-n}8 zKskc}CpXbmnU_zYpBDRI_v{Z%sCa8$1rO9RU+;x6on!-V?N&mJ4qmv>((=)GfRn^8 zmq!ktt{9R0_MzlTGt)-sd06483wDKW^?Z;n*K|okJttdY^!HJ-w`Wt!#a@R7pms0W zD{2llpImZxN0lF1l#~&+;-DJ@ZEct{@+vRK|JOR+vLbNh886t^&Dww0CJnl-R#0qH zqBOIG{j*)hP~f%}A~w>D#JF;Hl#^0I_NbqndYjcG^gIrkW}x0el1Y_)dTFm-axt$J zE4_55IH^~%9<7(?=JoG}3kxUC`K0~aIW)yaL~CRH<01Ow0QZpX$9wY}FBDy)&bWMt zscuZ*hOhDh%#0AVHyp%6LU%r#_#sT(Q5w~EorS>#%$J# zf8k{3C7ijwp?&JnH^1cMoxF^x!DU}1LME&I`?;8G%wEE@Aun5>5s@;i&xUN=6`s<- zpVinTA~RxpMUlI4qppB^dCeJSlnbS6z-e|3eE`EVn>`t-T>6fth}eQvVA6A&WFbtV z$(uKH1&9lp?@Qc*k5Hy^3lRa%@qxerM~Wy44R0mb39ieAsiOH(?$;E>x2|gIjQ^rq zf1F89Fx7^!RIUWQ>s{>ZiAc12>+=Cz7ktnpRZICwer|RhH@5^~SlO=X$qzbzY$+{c z0ho76WwtYXOs^8OcfKyWAUMgS_!2@B6UeK8$-m~AUZ~j}T;Jt3H+9rXP4Fsdm*_(_uXg88i)i^xCQOD18EVkv#h} z$UvQTgOJWYX(o4y?Di}8uy14MJdw8VSTCpb8dn~(&G9R-~7s4*sFSMSYmV1Q&!Xam=iVC5+FG>{fJXd8~tPLSRhZpD^;N%O)%i>sH`aaPfT{MN<|6obm(~{dN{S7n_vwyowR)v;Ip%qH zD^0V8{;L}aeHN_84ij@2%b7IV8giWnR86D~uH!3KLhoeKz$0Hh_X0Lwp7p3G*^$R= z7#9s_=v|5BIN!N-2g=;1v^P05+v#)F+ETQ-oKuDfn}8 zy1`N%w7r8k1sB+vEFmju1Ru>I<^lp!B9GM}MSH~CtT}x@g)!J`S^fwV>bF!&gs2Vo zLB!Ik1K(0Oa8!D@zlK^8?lw{vtNWBxb;NtHRjs>a`&rvi`A7*?%0z<)2U&>aF4CX& z%5QTlnk0hNoWfLy+SIRQPb>$50|KWl8ADjiuzR-LhN7nGP7WM;~OfsM|ShD|%u#h$Qecgw(qEEjvmPMuu41*jY)OM{QxXhYhEoa+w<;m;MPOM6K9IX>zlePVly}oOCzle9e_g4&Cwqp^7 zHyht91a1<}PV(Ci+i7u8qCL5}7hJ757k#o2&R4~T1+5twBsP1m52hPEJOiwn8Rb`@ zko-K!h$gHHszYad!bCnJSvDTMiSbE2ZRr%h>NAJVdKbRmYbmEXDaUc$Ha8pG>fIPZAF-djqh;Y7d%h@Ss^Y@QAZ*~tvg2pGvTS;3%%xsSh+jgGA5?ONk9`TsUF z3=cOnB<{c-v)EjGU)f^1EG}!5C2ogArwE*mS!w*r04rD@CHKVaWs>f2!6XD5*Hi1W zKJgG?Jfm%%g?E}v9&nl6+4S2>rVZ{g5*Ij}(Jvaux zI!Ow0$}S`&cd~uy=-6t-)qAZtrv|<23W(NqQ!RPS7!u+X12)WgD^}s`UuQ#0hY9u5 zUt);v?mvSMwccjNmJ-Cn!bz=j-@FOC@sf>O9e0_46!F6NA^;xwG(?GE!@cEIMu*S7 zJ4?Oqeb&|Q)$y!#P^3N1E?X0nw7HBCLj7C%^yU&Zb3%;oy8(GsXl_Y8a<;6Mz=NA% zr92*n;~ii>iBXe39$0F%cP#eMkL>B9=Yb5e4D+5b<^$kQZ}nR-f^Sd(X8=_P;vv2m zWGCpVl{8vm%PE$gkr9sSLl}j6cg;Q@vG`?eqK_l!ps^rrdXcq;2q{9}1ecbaeJ~%I zF4@sA!-1b~lgbcgzIfcbgh|&qFGTo7ui069F@6zc8M-FeEx`4wb?t!+$f*X0yPusU zj&(O&|3%$g|Ha|C=W%&L{~cBWgHA5ua;Sw(6Rc9%tOW{m1xQhfpw z?9)+VW!H~r|L!j7H_$tL*xr60=gU2oe$#0DmN$2VbzM*H z<5R*c|6-3FL7u&XKgsc2nr<(0KE4}@g2`SKYA-Z%eIO|>EDwOMx)Xl5^_!h3&Kfle z7wx&F7T3gzTi*=WJufT71{NAd5(n!kvzIJGKVABhM6DC%jbB(Mh8l>Zm&db+HdwLm z#d8QXhx?WW41Li5*@DIT+uGgS3cPQ^q)bt5uvSJHW^@e0s~urs-nwLv2BVF>9tiGx z`WlOWNY9es)+cXx-uwN7Wz!8q`*-<7w_3g+!?rYH*)@uL2Y2b*G7Ge8&n`)B?o!xXUb}TJ@9gY{ z1fTsS>oN^vi8)WZqGyqthBfu&4b}w5_>|P7;L(`RN5%1I?@OG{(oDBr|H^V}nn;yH zrMGPwp!SS(wsk=#bQTs?A5{Tphqbk|ztBsU?#%;-Ls#o`HeR-!P&=%m3f`?nU>q<^EGhe=cvbhTF#J}Gt(n=)Di7(0mye~#byc9820a_i}^A<6y zF);es?bumgTldMdH>!KTA*iSRoDU|rd3MCBGUu>=Q6%!15v~_kfIWu$9oD9M0fl3$ zddD9U8r@~1YPYd^XZ2F?1VDdlpp@evqi~gsa>toptX1@2xm4rUu}=KeKjBuQR!PODtl+TVH&x@> zBz5EL0?Xz5LNU{Vf=`wty?W!{!*f{mB}`st58H{m)b)tGe&E?@U-qzxkklc^F$Fkx z)@Qu$iS+n>t*zx3ZDyc_zb8rQA~c2Jy*#<$yxm=VWM|EU_ni1c>%@z^Q|-)0z7{#B zP^BzRP>N>M(^^zxnp$I@a)=EY|GqalhMP~r41wzsR~Z5V=|!6qda<)rY=UfSzV~=2 zD)*=69$KiA5GHUC&~v$t3IiQ|%6=5EeXi?*+pVv0IPDGO*Z3?#H~myq!y@cVo4tmT zM>f<m2-a<5v|zFmTB$=n14vU@7|(G}rXBn}95UQLA`Q@>-&?qaAg1UAf2I z8eOWQ@w$lUT*vFpD#KepcX)2~z zN_NQxwhSA%(zhDP*_~-mF7Vx|JOsZdThWBh%K~O1_5*;4<@=AM6W4nkefFx%-92Pt7%+D*Q(AVv4NSstfU*REiEx}uk?V}Ua@qzB=)-|O0(&UQ zHZLTV?3C~Wsjw7rjIVl7`P2%=HHR!C?Z|7a8yCdo>&wZYPbbE&8IY~qe@YMkP5i*| z!aH;gKRp(J?^NIJq`*gb((fLW_j_s9tE(IX(oPTmE2u5VZt+*s37QR;evIqe3ywWb zgo}v(G<0{-j3;ekh?JaOn=SKHI!g9*cWu{NEkKN8(sD**KBZR%TFS$&DVJ*zb}WNT z!U)(7AJ~ACiy};sEpbN)fg-oRl^IqrkTU+cOmXGFzAXkN{GFo2wbvKFuU`+T7A*xD z0S*Tn8U(Qv^q?aHe$?vbSeRx&;nmpSO~Aw|KUN2Jjj@I)(A#O7nU@L@n23hEMKfZm zj@J9v+juK|21G*%q%~q5k<^gB$N=P*-*Y-dfHqGtp~UtpA>eW!_(YJp0RfyBn1Byw zHu^0Z(C*RUY2saane9Cy`x-YyTTc6LZD+Q%bfqcbs(1Rt()(?sQ)GXU96#%#{mpY^ z*_Ke&MA%)QS74->V9QO;u{3WVcC6X`PZaJ~ZO9gCw;#hTZYCTzDG6ye!PQ(cy2Gh# z=y!7<+V3O56FUqLI3^|>(2Ej`FB@Vp?|5ckQLlQP(i#VBsm9~H!WBT1BE3GHv>nd< z^76K>3tBgCkM)VaKOH|w-HMRkJO%&$&}2^=)B6$`iexGDVPPs2X#4;knYS9j#3Xdn z9!Fq4Ro$IwQfx;4pz?Q1-s|092zEJW+6q%5t>17PN~fxr87SFF+TVgQc&HXZoqn~{-zz229qtCcV(Itbo-acP)ffhCvVfoKJ}n2o(LKz|N46D{ex`2N)#t)cqBloAo7*Q5ma2#vlf&3xh1@-|*ZY*ePP2R0K z$!aCoxXVmGad|HUGWOW$0H6TAFO;?_M9^)Ihe{LX;&nmBS`Y@PcW=Tk5JI+R_rZ2X zQ`vlZs2Rk!m7wh?1zFj?#FFFeFT+@QWj#MfGYPC!p{lnPW9HB0uAkcg7BL+KUtXmp z8XYXGIN3|8Rj=c&ksGNHGsRyI4zUN1P$4@SoBhYe-qOwwUu9`8MbHqY7`&MGuS>Ya zcO&^8N%blu)w${uc(*MR-){d^9hP;lQgWwYAZSF+#_A;r#;gW}cmothMwe!*+@28X zo*s9aa;hRG91y)$$t|aArP7BpKy& z0Ug77(4Zg12FY!xIB+#5!9CJIPU--?2f0ZK-gv*5O-=WviDmX|8R7HuunE&-bv5aD zG{SxH}bF^<#2->+0ja>@z(}4fDw}TVzCjcEDu> z%gF$dP=bR!0hroh%-p}n-6`b9j~)jnM(-zWF|Q|{?#y?N*d_JH7+*zPVZ`71PmE0w zf`q$S$)Kwq3f1+q=GwQ2Qp0dt9NS+L{ZZ@H<5Cucp?J{j0(U2B2tMRjI>$4Y5q;TzlUt=sUl!*se*XK1npO-MXdNN1IXFV?+nZMQR@(8JDCK(Y zCH&xEa$z_R!AOih#(-g$Y_fz2p$emVd+{)T-~NPnH-RWwM={%VWKIsabXXUr zvGQxPE@>H{+=rorKUyh3d_g<_nitQ;Z#(10Ynk5Z z!A5Q5WDHqoeR{kG4uN5wzo_sC5!B9v$rxls{kxpZy5t9#2h$D(CA48&I9!Pz!y~#~ z`TCFKNhrWH6}p7^!Zps==382Rmy)O-x7dSiY-zg$ zO6Fl#cbHC-g}jO@r)tN@^#=wq~Ulgbj+u zql?o~IJUxctXTg-4WwwV|9rjXcwvM67u&1t&6P>?$*WJ#q{*WJmK3E-(3mZ**ZjeizM0*sJVrq6w&ZrUmkTx~i|LsonFH z3^yGp1ysy~9cNV=+J8>!awG-Mg10`WzMZO0V)Ka&?J9u_7PQa@{@zG#%c-3k&UyS! z+vqq0SopYx0ocZ{aTA1}>%ng9l zC3Ic+KX@%at8c`gBb{)#a22OJQ-zSoS|=6lff|Z$Dd%a|?uq`z}EW*BXFa2jy0LQRgVT ztw6m>f9$!=%5uYw;wrJKk!nkwX}-ks%TV#d2~xRmjF{RO4J+`_-(0S_E`fZCk({|G zT>pC-Gx>RE_$&q!&1pXbgsQSpg<7xj#cYffk*L%@|3>SP&Iq()kA!}NfMS7>-1flI`~{V8fG6>;jQgyU76V< z$o}fxuiKpwAqpH55={@>!yjorI@W7;0R2~&??dn``Ir!AB~gJRjaBW_?k**`eYQoI z0}F|n=o`T%r8|NE!$*0L>(9CFE+YBpY55PU36he4GjU+_u8Fkm0#vM1M4FJaLK5^b zst88KLrhv5sLg9Sw3*DVQpz=$pBM3smB99MuZ8}r(-!`$l;?`i9UG=c_^{WXuGmgD zbq;sgbYmcc3dYGB5gnAv(pNx-yI%0(Pj7=*FmTly!y`ysdagk2>m>!vZ%@iU-JdGF z{rECQv3%F1(8-TtwM)K}+s?1v5Egzyz(r#Fy1+poF|6e5%d;GXh@Ai@JfNCD;#>7D z$bdIy0l8OftNSiVilJ&9N)2R2c>#vb(SwZ{b$D<#EV%$H%WSLD?=eX?^&JJFC zl3BW%$FEn9Vo4K2$wAqXkGdN`1479mk%JhGhu7cYCozU*s;y($xqNLD{2D3qZU2D9 z-M?i1F}_M^fM{N7`JtN8wnBOPq*RNf!T@D{%XZuC)|(R-q3YY!oE$LKQ1;Oo{0_y>mku{gjRfWnio z8`75eB6=jKCb3qYm#$Vr`^9OFo5ER&Rnb!aBl?fhtnWn0#Q@A0mkpygL6`Z$~zT;Rq#Q zin3MANyd-7SC|z&#B2chXIY=Ztfb)0k^)!j0ekTZZ_y6;fM1XiieYnqcX!U+f2B|6 z&1LlnzR#wb2xU{9MLTGBq$xSh=WC`WO;LRm8(n)MDff&qZ|pN4jE6IY_Dc|NsXtZ9 zvt26oC=GLg5C(MW#OBDdSJ%9r%ktc8%j-d0D@pH`zbCC)-83ZxMsGhouyINpNKDH6 zP5q3o=IoB@CdT`-Z(&>*f6r3E?J=H1HM${_m4Sun`C4HHLf(Z#w}Ss6yGdW1PJSYZ zIP1U5G5{$sSLs!G-Ml>~;t)7HUEMeylm5<~xVi2>&tedd@@UrjMM}$Rq2Xt&+ps|K zgx}hPJT`CssGn<(YwDivatfnD?`Fb~8x3!&u11x6GV~V}1(uOlqRLG|e9GjSbgYGf zhi@}}CrB7wtYeJ2;O6oPQ%5Ms;b~WN4}FUhwT0VE{lM-_W;(zy_D(BL4q|`i-ts7V z{MnGo{Z5~w0%h)VQ1YCN?Hr_On4Qq*4E)1M-?+^gC~jU8`51SO(YJVlB%6Bd>rdM* z;K~YC2k&`E{1*S+K;7%*1C};V%GGHCT1x#jRW*XB$a{|tXHlz?Og>M)PY(vxMk_b9 zukAwShKY$tjrAf!olO~&0&yAEJ^yWl5p?TC2mw2|azGa^FoQ7Hu^~0p5rNgJ<~8BO ztBEdL0F95rE#~9%ByIKPTFoq(bp2I6Wv5wu^7=sxkiVRea@)D&tU7iW=|s3GW2XWK2#RdX#cA}aO>|*A3ITWl+-!&jrSEK7ILL| z8>ko0%+UDz-QJ3g6}B!ps17;UxfkMSjQv`%YvKEwE_qu#LXcb+oUJV;e)C6&#Nm(d zp|mM|s4WJS&<;KE*BfhLX)hT|f}@`PD7?$wzTyiuyllX}Xcmx@a>;MUjI$L1$H!rc zV12(MCOH)YcAfp?zQ)a3+QrHQ_>2!AW1!Jc&jcHv%!L z5hP9yn*7&+b9v*b_W)SaNE7$*AdZ5JG?AGDZ@++Hx4sCO?-vPpTGZ4eG1xhM6oxjYDJkbVI_q(6l;1J|Dv=(h5MzSbol z;MqGYbou@|=dlE5F3H~e2;ywH- z3YqGY3X!%+M@^EHC_H+}aoFo`ZSyO!$}`$I&H(zcPaA6QRuaPM@&`D&>{xXXn$ z#QsrpJkfdMdl%%XDH->a9f-p$XB^XF@de&-)JGJuKk?rL<2N(Xwo_zK*ny(9=h7Bj zqS3Btn|zy8G00>z?LG0}N$xo9*H^_njdAP*{t{=O4Ge78pBXiuc2}F-9(DN#oMPxg z+x^DLH;0?ex!}+Uy>$*s!RqF2+>i1kL3I~2qTzYGo2!9Uy9Q35=37#&6mAyHBG9A^ zeVDy(;>uk9>H%5U7b&XW8#c+I#gPTvpjZDUiV5Vj&6AF@-OW}!_`nw6%rKA=C*|SP zF$eOXI~QM;fD|_mza9Jb-~5zR;G7}GlHLhu@-D&=BA$im`Zy%rqTn&+R&CSMH<*FA zEFk4mekyNl1F|6qct7x;!4dM~ z#?|MG#w~BljruvSTKfM(L%sh?1IEsF=MO-J)US=4FG>e76W0JOb$zuO75k|F2LR9f AZvX%Q diff --git a/frontend/app-images/favicons/safari-pinned-tab.svg b/frontend/app-images/favicons/safari-pinned-tab.svg deleted file mode 100644 index ab8289002..000000000 --- a/frontend/app-images/favicons/safari-pinned-tab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/app-images/favicons/site.webmanifest b/frontend/app-images/favicons/site.webmanifest deleted file mode 100644 index 99d1016eb..000000000 --- a/frontend/app-images/favicons/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "", - "short_name": "", - "icons": [ - { - "src": "/images/favicons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/images/favicons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/frontend/app-images/logo-256.png b/frontend/app-images/logo-256.png deleted file mode 100644 index 0ea6dd3d473df74d9cf1d03d2614f61e0de126de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29709 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 diff --git a/frontend/fonts/feather b/frontend/fonts/feather deleted file mode 120000 index 440203ba2..000000000 --- a/frontend/fonts/feather +++ /dev/null @@ -1 +0,0 @@ -../node_modules/tabler-ui/dist/assets/fonts/feather \ No newline at end of file diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff deleted file mode 100644 index 96d8768ea9a10f004e2215a1a674287c8c7abfe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31740 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff deleted file mode 100644 index 0829caef1eb96f768bedf713c983e2a3904ad835..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28540 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 deleted file mode 100644 index 7c901cd8450cecf93767560eecf4a3dc6b0cefb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22240 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff deleted file mode 100644 index 99652481a07f237145d733e06e6b2c0b926dfafc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28744 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 DnjJ_~ diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 deleted file mode 100644 index 343e5ba8d3f924aef2e589f25657cb97ff423ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22436 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 diff --git a/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 b/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 deleted file mode 100644 index d552543be16ee93a7c66358c9cc80540a3b11860..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25656 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 jcp0p9&3Un?g -<%- include partials/header.ejs %> - -

- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/login.ejs b/frontend/html/login.ejs deleted file mode 100644 index 2fb2b1193..000000000 --- a/frontend/html/login.ejs +++ /dev/null @@ -1,9 +0,0 @@ -<% var title = 'Login – NPMplus' %> -<%- include partials/header.ejs %> - -
- -
- - -<%- include partials/footer.ejs %> diff --git a/frontend/html/partials/footer.ejs b/frontend/html/partials/footer.ejs deleted file mode 100644 index 7fb2bd61b..000000000 --- a/frontend/html/partials/footer.ejs +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/frontend/html/partials/header.ejs b/frontend/html/partials/header.ejs deleted file mode 100644 index 9a4bce7d5..000000000 --- a/frontend/html/partials/header.ejs +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - <%- title %> - - - - - - - - - - - - - - - - - diff --git a/frontend/images b/frontend/images deleted file mode 120000 index 37c318540..000000000 --- a/frontend/images +++ /dev/null @@ -1 +0,0 @@ -./node_modules/tabler-ui/dist/assets/images \ No newline at end of file diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js deleted file mode 100644 index c9d554f10..000000000 --- a/frontend/js/app/api.js +++ /dev/null @@ -1,757 +0,0 @@ -const $ = require('jquery'); -const _ = require('underscore'); -const Tokens = require('./tokens'); - -/** - * @param {String} message - * @param {*} debug - * @param {Number} code - * @constructor - */ -const ApiError = function (message, debug, code) { - let temp = Error.call(this, message); - temp.name = this.name = 'ApiError'; - this.stack = temp.stack; - this.message = temp.message; - this.debug = debug; - this.code = code; -}; - -ApiError.prototype = Object.create(Error.prototype, { - constructor: { - value: ApiError, - writable: true, - configurable: true - } -}); - -/** - * - * @param {String} verb - * @param {String} path - * @param {Object} [data] - * @param {Object} [options] - * @returns {Promise} - */ -function fetch(verb, path, data, options) { - options = options || {}; - - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') { - data = JSON.stringify(data); - } - - $.ajax({ - url: url, - data: typeof data === 'object' ? JSON.stringify(data) : data, - type: verb, - dataType: 'json', - contentType: options.contentType || 'application/json; charset=UTF-8', - processData: options.processData || true, - crossDomain: true, - timeout: options.timeout ? options.timeout : 180000, - xhrFields: { - withCredentials: true - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data, textStatus, response) { - let total = response.getResponseHeader('X-Dataset-Total'); - if (total !== null) { - resolve({ - data: data, - pagination: { - total: parseInt(total, 10), - offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), - limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) - } - }); - } else { - resolve(response); - } - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -/** - * - * @param {Array} expand - * @returns {String} - */ -function makeExpansionString(expand) { - let items = []; - _.forEach(expand, function (exp) { - items.push(encodeURIComponent(exp)); - }); - - return items.join(','); -} - -/** - * @param {String} path - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ -function getAllObjects(path, expand, query) { - let params = []; - - if (typeof expand === 'object' && expand !== null && expand.length) { - params.push('expand=' + makeExpansionString(expand)); - } - - if (typeof query === 'string') { - params.push('query=' + query); - } - - return fetch('get', path + (params.length ? '?' + params.join('&') : '')); -} - -function FileUpload(path, fd) { - return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - let token = Tokens.getTopToken(); - - xhr.open('POST', '/api/' + path); - xhr.overrideMimeType('text/plain'); - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - xhr.send(fd); - - xhr.onreadystatechange = function () { - if (this.readyState === XMLHttpRequest.DONE) { - if (xhr.status !== 200 && xhr.status !== 201) { - try { - reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message)); - } catch (err) { - reject(new Error('Upload failed: ' + xhr.status)); - } - } else { - resolve(xhr.responseText); - } - } - }; - }); -} - -//ref : https://codepen.io/chrisdpratt/pen/RKxJNo -function DownloadFile(verb, path, filename) { - return new Promise(function (resolve, reject) { - let api_url = '/api/'; - let url = api_url + path; - let token = Tokens.getTopToken(); - - $.ajax({ - url: url, - type: verb, - crossDomain: true, - xhrFields: { - withCredentials: true, - responseType: 'blob' - }, - - beforeSend: function (xhr) { - xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); - }, - - success: function (data) { - var a = document.createElement('a'); - var url = window.URL.createObjectURL(data); - a.href = url; - a.download = filename; - document.body.append(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - }, - - error: function (xhr, status, error_thrown) { - let code = 400; - - if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { - error_thrown = xhr.responseJSON.error.message; - code = xhr.responseJSON.error.code || 500; - } - - reject(new ApiError(error_thrown, xhr.responseText, code)); - } - }); - }); -} - -module.exports = { - status: function () { - return fetch('get', ''); - }, - - Tokens: { - - /** - * @param {String} identity - * @param {String} secret - * @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful - * @returns {Promise} - */ - login: function (identity, secret, wipe) { - return fetch('post', 'tokens', {identity: identity, secret: secret}) - .then(response => { - if (response.token) { - if (wipe) { - Tokens.clearTokens(); - } - - // Set storage token - Tokens.addToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - }, - - /** - * @returns {Promise} - */ - refresh: function () { - return fetch('get', 'tokens') - .then(response => { - if (response.token) { - Tokens.setCurrentToken(response.token); - return response.token; - } else { - Tokens.clearTokens(); - throw(new Error('No token returned')); - } - }); - } - }, - - Users: { - - /** - * @param {Number|String} user_id - * @param {Array} [expand] - * @returns {Promise} - */ - getById: function (user_id, expand) { - return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); - }, - - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('users', expand, query); - }, - - /** - * @param {Object} data - * @returns {Promise} - */ - create: function (data) { - return fetch('post', 'users', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'users/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'users/' + id); - }, - - /** - * - * @param {Number} id - * @param {Object} auth - * @returns {Promise} - */ - setPassword: function (id, auth) { - return fetch('put', 'users/' + id + '/auth', auth); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - loginAs: function (id) { - return fetch('post', 'users/' + id + '/login'); - }, - - /** - * - * @param {Number} id - * @param {Object} perms - * @returns {Promise} - */ - setPermissions: function (id, perms) { - return fetch('put', 'users/' + id + '/permissions', perms); - } - }, - - Nginx: { - - ProxyHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/proxy-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/proxy-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/proxy-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/proxy-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); - } - }, - - RedirectionHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/redirection-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/redirection-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/redirection-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/redirection-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); - } - }, - - Streams: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/streams', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/streams', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/streams/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/streams/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/streams/' + id + '/disable'); - } - }, - - DeadHosts: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/dead-hosts', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/dead-hosts', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/dead-hosts/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - get: function (id) { - return fetch('get', 'nginx/dead-hosts/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - setCerts: function (id, form_data) { - return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - enable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - disable: function (id) { - return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); - } - }, - - AccessLists: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/access-lists', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - return fetch('post', 'nginx/access-lists', data); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/access-lists/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/access-lists/' + id); - } - }, - - Certificates: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('nginx/certificates', expand, query); - }, - - /** - * @param {Object} data - */ - create: function (data) { - - const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0); - return fetch('post', 'nginx/certificates', data, {timeout}); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'nginx/certificates/' + id, data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - delete: function (id) { - return fetch('delete', 'nginx/certificates/' + id); - }, - - /** - * @param {Number} id - * @param {FormData} form_data - * @params {Promise} - */ - upload: function (id, form_data) { - return FileUpload('nginx/certificates/' + id + '/upload', form_data); - }, - - /** - * @param {FormData} form_data - * @params {Promise} - */ - validate: function (form_data) { - return FileUpload('nginx/certificates/validate', form_data); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - renew: function (id, timeout = 180000) { - return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout}); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - testHttpChallenge: function (domains) { - return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({ - domains: JSON.stringify(domains), - })); - }, - - /** - * @param {Number} id - * @returns {Promise} - */ - download: function (id) { - return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip") - } - } - }, - - AuditLog: { - /** - * @param {Array} [expand] - * @param {String} [query] - * @returns {Promise} - */ - getAll: function (expand, query) { - return getAllObjects('audit-log', expand, query); - } - }, - - Reports: { - - /** - * @returns {Promise} - */ - getHostStats: function () { - return fetch('get', 'reports/hosts'); - } - }, - - Settings: { - - /** - * @param {String} setting_id - * @returns {Promise} - */ - getById: function (setting_id) { - return fetch('get', 'settings/' + setting_id); - }, - - /** - * @returns {Promise} - */ - getAll: function () { - return getAllObjects('settings'); - }, - - /** - * @param {Object} data - * @param {Number} data.id - * @returns {Promise} - */ - update: function (data) { - let id = data.id; - delete data.id; - return fetch('put', 'settings/' + id, data); - } - } -}; diff --git a/frontend/js/app/audit-log/list/item.ejs b/frontend/js/app/audit-log/list/item.ejs deleted file mode 100644 index 84743c8d4..000000000 --- a/frontend/js/app/audit-log/list/item.ejs +++ /dev/null @@ -1,80 +0,0 @@ - -
- -
- - -
- <% if (user.is_deleted) { - %> - <%- user.name %> - <% - } else { - %> - <%- user.name %> - <% - } - %> -
- - -
- <% - var items = []; - switch (object_type) { - case 'proxy-host': - %> <% - items = meta.domain_names; - break; - case 'redirection-host': - %> <% - items = meta.domain_names; - break; - case 'stream': - %> <% - items.push(meta.incoming_port); - break; - case 'dead-host': - %> <% - items = meta.domain_names; - break; - case 'access-list': - %> <% - items.push(meta.name); - break; - case 'user': - %> <% - items.push(meta.name); - break; - case 'certificate': - %> <% - if (meta.provider === 'letsencrypt') { - items = meta.domain_names; - } else { - items.push(meta.nice_name); - } - break; - } - %> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> - — - <% - if (items && items.length) { - items.map(function(item) { - %> - <%- item %> - <% - }); - } else { - %> - #<%- object_id %> - <% - } - %> -
-
- <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> -
- - - <%- i18n('audit-log', 'view-meta') %> - diff --git a/frontend/js/app/audit-log/list/item.js b/frontend/js/app/audit-log/list/item.js deleted file mode 100644 index 862ffc221..000000000 --- a/frontend/js/app/audit-log/list/item.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - meta: 'a.meta' - }, - - events: { - 'click @ui.meta': function (e) { - e.preventDefault(); - Controller.showAuditMeta(this.model); - } - }, - - templateContext: { - more: function() { - switch (this.object_type) { - case 'redirection-host': - case 'stream': - case 'proxy-host': - return this.meta.domain_names.join(', '); - } - - return '#' + (this.object_id || '?'); - } - } -}); diff --git a/frontend/js/app/audit-log/list/main.ejs b/frontend/js/app/audit-log/list/main.ejs deleted file mode 100644 index ec3cf2a2b..000000000 --- a/frontend/js/app/audit-log/list/main.ejs +++ /dev/null @@ -1,9 +0,0 @@ - -   - User - Event -   - - - - diff --git a/frontend/js/app/audit-log/list/main.js b/frontend/js/app/audit-log/list/main.js deleted file mode 100644 index 9d3e26fb1..000000000 --- a/frontend/js/app/audit-log/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/audit-log/main.ejs b/frontend/js/app/audit-log/main.ejs deleted file mode 100644 index 8d182b595..000000000 --- a/frontend/js/app/audit-log/main.ejs +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
-

<%- i18n('audit-log', 'title') %>

-
- -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/audit-log/main.js b/frontend/js/app/audit-log/main.js deleted file mode 100644 index 0d03c5ca8..000000000 --- a/frontend/js/app/audit-log/main.js +++ /dev/null @@ -1,82 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const AuditLogModel = require('../../models/audit-log'); -const ListView = require('./list/main'); -const template = require('./main.ejs'); -const ErrorView = require('../error/main'); -const EmptyView = require('../empty/main'); - -module.exports = Mn.View.extend({ - id: 'audit-log', - template: template, - - ui: { - list_region: '.list-region', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.AuditLog.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AuditLogModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showAuditLog(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - this.showChildView('list_region', new EmptyView({ - title: App.i18n('audit-log', 'empty'), - subtitle: App.i18n('audit-log', 'empty-subtitle') - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['user'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['user']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/audit-log/meta.ejs b/frontend/js/app/audit-log/meta.ejs deleted file mode 100644 index 98a2d9734..000000000 --- a/frontend/js/app/audit-log/meta.ejs +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/frontend/js/app/audit-log/meta.js b/frontend/js/app/audit-log/meta.js deleted file mode 100644 index 815cdfac9..000000000 --- a/frontend/js/app/audit-log/meta.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./meta.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide' -}); diff --git a/frontend/js/app/cache.js b/frontend/js/app/cache.js deleted file mode 100644 index 6d1fbc4f9..000000000 --- a/frontend/js/app/cache.js +++ /dev/null @@ -1,10 +0,0 @@ -const UserModel = require('../models/user'); - -let cache = { - User: new UserModel.Model(), - locale: 'en', - version: null -}; - -module.exports = cache; - diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js deleted file mode 100644 index ccb2978a8..000000000 --- a/frontend/js/app/controller.js +++ /dev/null @@ -1,447 +0,0 @@ -const Backbone = require('backbone'); -const Cache = require('./cache'); -const Tokens = require('./tokens'); - -module.exports = { - - /** - * @param {String} route - * @param {Object} [options] - * @returns {Boolean} - */ - navigate: function (route, options) { - options = options || {}; - Backbone.history.navigate(route.toString(), options); - return true; - }, - - /** - * Login - */ - showLogin: function () { - window.location = '/login'; - }, - - /** - * Users - */ - showUsers: function () { - let controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './users/main'], (App, View) => { - controller.navigate('/users'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * User Form - * - * @param [model] - */ - showUserForm: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Permissions Form - * - * @param model - */ - showUserPermissions: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './user/permissions'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Password Form - * - * @param model - */ - showUserPasswordForm: function (model) { - if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { - require(['./main', './user/password'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * User Delete Confirm - * - * @param model - */ - showUserDeleteConfirm: function (model) { - if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { - require(['./main', './user/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dashboard - */ - showDashboard: function () { - let controller = this; - - require(['./main', './dashboard/main'], (App, View) => { - controller.navigate('/'); - App.UI.showAppContent(new View()); - }); - }, - - /** - * Nginx Proxy Hosts - */ - showNginxProxy: function () { - if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { - let controller = this; - - require(['./main', './nginx/proxy/main'], (App, View) => { - controller.navigate('/nginx/proxy'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Proxy Host Form - * - * @param [model] - */ - showNginxProxyForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Host Delete Confirm - * - * @param model - */ - showNginxProxyDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { - require(['./main', './nginx/proxy/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Redirection Hosts - */ - showNginxRedirection: function () { - if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { - let controller = this; - - require(['./main', './nginx/redirection/main'], (App, View) => { - controller.navigate('/nginx/redirection'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Redirection Host Form - * - * @param [model] - */ - showNginxRedirectionForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Proxy Redirection Delete Confirm - * - * @param model - */ - showNginxRedirectionDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { - require(['./main', './nginx/redirection/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Stream Hosts - */ - showNginxStream: function () { - if (Cache.User.isAdmin() || Cache.User.canView('streams')) { - let controller = this; - - require(['./main', './nginx/stream/main'], (App, View) => { - controller.navigate('/nginx/stream'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Stream Form - * - * @param [model] - */ - showNginxStreamForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Stream Delete Confirm - * - * @param model - */ - showNginxStreamDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { - require(['./main', './nginx/stream/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Dead Hosts - */ - showNginxDead: function () { - if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { - let controller = this; - - require(['./main', './nginx/dead/main'], (App, View) => { - controller.navigate('/nginx/404'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Dead Host Form - * - * @param [model] - */ - showNginxDeadForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Dead Host Delete Confirm - * - * @param model - */ - showNginxDeadDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { - require(['./main', './nginx/dead/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Help Dialog - * - * @param {String} title - * @param {String} content - */ - showHelp: function (title, content) { - require(['./main', './help/main'], function (App, View) { - App.UI.showModalDialog(new View({title: title, content: content})); - }); - }, - - /** - * Nginx Access - */ - showNginxAccess: function () { - if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { - let controller = this; - - require(['./main', './nginx/access/main'], (App, View) => { - controller.navigate('/nginx/access'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Access List Form - * - * @param [model] - */ - showNginxAccessListForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Access List Delete Confirm - * - * @param model - */ - showNginxAccessListDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { - require(['./main', './nginx/access/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Nginx Certificates - */ - showNginxCertificates: function () { - if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { - let controller = this; - - require(['./main', './nginx/certificates/main'], (App, View) => { - controller.navigate('/nginx/certificates'); - App.UI.showAppContent(new View()); - }); - } - }, - - /** - * Nginx Certificate Form - * - * @param [model] - */ - showNginxCertificateForm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/form'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Renew - * - * @param model - */ - showNginxCertificateRenew: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/renew'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Delete Confirm - * - * @param model - */ - showNginxCertificateDeleteConfirm: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/delete'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Certificate Test Reachability - * - * @param model - */ - showNginxCertificateTestReachability: function (model) { - if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { - require(['./main', './nginx/certificates/test'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Audit Log - */ - showAuditLog: function () { - let controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/main'], (App, View) => { - controller.navigate('/audit-log'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Audit Log Metadata - * - * @param model - */ - showAuditMeta: function (model) { - if (Cache.User.isAdmin()) { - require(['./main', './audit-log/meta'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - }, - - /** - * Settings - */ - showSettings: function () { - let controller = this; - if (Cache.User.isAdmin()) { - require(['./main', './settings/main'], (App, View) => { - controller.navigate('/settings'); - App.UI.showAppContent(new View()); - }); - } else { - this.showDashboard(); - } - }, - - /** - * Settings Item Form - * - * @param model - */ - showSettingForm: function (model) { - if (Cache.User.isAdmin()) { - if (model.get('id') === 'default-site') { - require(['./main', './settings/default-site/main'], function (App, View) { - App.UI.showModalDialog(new View({model: model})); - }); - } - } - }, - - /** - * Logout - */ - logout: function () { - Tokens.dropTopToken(); - this.showLogin(); - } -}; diff --git a/frontend/js/app/dashboard/main.ejs b/frontend/js/app/dashboard/main.ejs deleted file mode 100644 index c00aa6d0f..000000000 --- a/frontend/js/app/dashboard/main.ejs +++ /dev/null @@ -1,67 +0,0 @@ - - -<% if (columns) { %> -
- <% if (canShow('proxy_hosts')) { %> - - <% } %> - - <% if (canShow('redirection_hosts')) { %> - - <% } %> - - <% if (canShow('streams')) { %> - - <% } %> - - <% if (canShow('dead_hosts')) { %> - - <% } %> -
-<% } %> diff --git a/frontend/js/app/dashboard/main.js b/frontend/js/app/dashboard/main.js deleted file mode 100644 index c2e82f855..000000000 --- a/frontend/js/app/dashboard/main.js +++ /dev/null @@ -1,92 +0,0 @@ -const Mn = require('backbone.marionette'); -const Cache = require('../cache'); -const Controller = require('../controller'); -const Api = require('../api'); -const Helpers = require('../../lib/helpers'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - id: 'dashboard', - columns: 0, - - stats: {}, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - e.preventDefault(); - Controller.navigate($(e.currentTarget).attr('href'), true); - } - }, - - templateContext: function () { - let view = this; - - return { - getUserName: function () { - return Cache.User.get('nickname') || Cache.User.get('name'); - }, - - getHostStat: function (type) { - if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { - return Helpers.niceNumber(view.stats.hosts[type]); - } - - return '-'; - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - }, - - columns: view.columns - }; - }, - - onRender: function () { - let view = this; - - if (typeof view.stats.hosts === 'undefined') { - Api.Reports.getHostStats() - .then(response => { - if (!view.isDestroyed()) { - view.stats.hosts = response; - view.render(); - } - }) - .catch(err => { - console.log(err); - }); - } - }, - - /** - * @param {Object} [model] - */ - preRender: function (model) { - this.columns = 0; - - // calculate the available columns based on permissions for the objects - // and store as a variable - //let view = this; - let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; - - perms.map(perm => { - this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; - }); - - // Prevent double rendering on initial calls - if (typeof model !== 'undefined') { - this.render(); - } - }, - - initialize: function () { - this.preRender(); - this.listenTo(Cache.User, 'change', this.preRender); - } -}); diff --git a/frontend/js/app/empty/main.ejs b/frontend/js/app/empty/main.ejs deleted file mode 100644 index 11633dfcc..000000000 --- a/frontend/js/app/empty/main.ejs +++ /dev/null @@ -1,11 +0,0 @@ -<% if (title) { %> -

<%- title %>

-<% } - -if (subtitle) { %> -

<%- subtitle %>

-<% } - -if (link) { %> - <%- link %> -<% } %> diff --git a/frontend/js/app/empty/main.js b/frontend/js/app/empty/main.js deleted file mode 100644 index 74998d65b..000000000 --- a/frontend/js/app/empty/main.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - className: 'text-center m-7', - template: template, - - options: { - btn_color: 'teal' - }, - - ui: { - action: 'a' - }, - - events: { - 'click @ui.action': function (e) { - e.preventDefault(); - this.getOption('action')(); - } - }, - - templateContext: function () { - return { - title: this.getOption('title'), - subtitle: this.getOption('subtitle'), - link: this.getOption('link'), - action: typeof this.getOption('action') === 'function', - btn_color: this.getOption('btn_color') - }; - } - -}); diff --git a/frontend/js/app/error/main.ejs b/frontend/js/app/error/main.ejs deleted file mode 100644 index f7fd709bd..000000000 --- a/frontend/js/app/error/main.ejs +++ /dev/null @@ -1,7 +0,0 @@ - -<%= code ? '' + code + ' — ' : '' %> -<%- message %> - -<% if (retry) { %> -

<%- i18n('str', 'try-again') %> -<% } %> diff --git a/frontend/js/app/error/main.js b/frontend/js/app/error/main.js deleted file mode 100644 index 6fa85fc8c..000000000 --- a/frontend/js/app/error/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'alert alert-icon alert-warning m-5', - - ui: { - retry: 'a.retry' - }, - - events: { - 'click @ui.retry': function (e) { - e.preventDefault(); - this.getOption('retry')(); - } - }, - - templateContext: function () { - return { - message: this.getOption('message'), - code: this.getOption('code'), - retry: typeof this.getOption('retry') === 'function' - }; - } - -}); diff --git a/frontend/js/app/help/main.ejs b/frontend/js/app/help/main.ejs deleted file mode 100644 index 6fb79e667..000000000 --- a/frontend/js/app/help/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/frontend/js/app/help/main.js b/frontend/js/app/help/main.js deleted file mode 100644 index b0f54374c..000000000 --- a/frontend/js/app/help/main.js +++ /dev/null @@ -1,16 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog wide', - - templateContext: function () { - let content = this.getOption('content').split("\n"); - - return { - title: this.getOption('title'), - content: '

' + content.join('

') + '

' - }; - } -}); diff --git a/frontend/js/app/i18n.js b/frontend/js/app/i18n.js deleted file mode 100644 index c63cdc079..000000000 --- a/frontend/js/app/i18n.js +++ /dev/null @@ -1,23 +0,0 @@ -const Cache = ('./cache'); -const messages = require('../i18n/messages.json'); - -/** - * @param {String} namespace - * @param {String} key - * @param {Object} [data] - */ -module.exports = function (namespace, key, data) { - let locale = Cache.locale; - // check that the locale exists - if (typeof messages[locale] === 'undefined') { - locale = 'en'; - } - - if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { - return messages[locale][namespace][key](data); - } else if (locale !== 'en' && typeof messages['en'][namespace] !== 'undefined' && typeof messages['en'][namespace][key] !== 'undefined') { - return messages['en'][namespace][key](data); - } - - return '(MISSING: ' + namespace + '/' + key + ')'; -}; diff --git a/frontend/js/app/main.js b/frontend/js/app/main.js deleted file mode 100644 index e85b4f620..000000000 --- a/frontend/js/app/main.js +++ /dev/null @@ -1,155 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); -const Mn = require('../lib/marionette'); -const Cache = require('./cache'); -const Controller = require('./controller'); -const Router = require('./router'); -const Api = require('./api'); -const Tokens = require('./tokens'); -const UI = require('./ui/main'); -const i18n = require('./i18n'); - -const App = Mn.Application.extend({ - - Cache: Cache, - Api: Api, - UI: null, - i18n: i18n, - Controller: Controller, - - region: { - el: '#app', - replaceElement: true - }, - - onStart: function (app, options) { - console.log(i18n('main', 'welcome')); - - // Check if token is coming through - if (this.getParam('token')) { - Tokens.addToken(this.getParam('token')); - } - - // Check if we are still logged in by refreshing the token - Api.status() - .then(result => { - Cache.version = [result.version.major, result.version.minor, result.version.revision].join('.'); - }) - .then(Api.Tokens.refresh) - .then(this.bootstrap) - .then(() => { - console.info(i18n('main', 'logged-in', Cache.User.attributes)); - this.bootstrapTimer(); - this.refreshTokenTimer(); - - this.UI = new UI(); - this.UI.on('render', () => { - new Router(options); - Backbone.history.start({pushState: true}); - - // Ask the admin use to change their details - if (Cache.User.get('email') === 'admin@example.com') { - Controller.showUserForm(Cache.User); - } - }); - - this.getRegion().show(this.UI); - }) - .catch(err => { - console.warn('Not logged in:', err.message); - Controller.showLogin(); - }); - }, - - History: { - replace: function (data) { - window.history.replaceState(_.extend(window.history.state || {}, data), document.title); - }, - - get: function (attr) { - return window.history.state ? window.history.state[attr] : undefined; - } - }, - - getParam: function (name) { - name = name.replace(/[\[\]]/g, '\\$&'); - let url = window.location.href; - let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); - let results = regex.exec(url); - - if (!results) { - return null; - } - - if (!results[2]) { - return ''; - } - - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }, - - /** - * Get user and other base info to start prime the cache and the application - * - * @returns {Promise} - */ - bootstrap: function () { - return Api.Users.getById('me', ['permissions']) - .then(response => { - Cache.User.set(response); - Tokens.setCurrentName(response.nickname || response.name); - }); - }, - - /** - * Bootstraps the user from time to time - */ - bootstrapTimer: function () { - setTimeout(() => { - Api.status() - .then(result => { - let version = [result.version.major, result.version.minor, result.version.revision].join('.'); - if (version !== Cache.version) { - document.location.reload(); - } - }) - .then(this.bootstrap) - .then(() => { - this.bootstrapTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.bootstrapTimer(); - } - }); - }, 30 * 1000); // 30 seconds - }, - - refreshTokenTimer: function () { - setTimeout(() => { - return Api.Tokens.refresh() - .then(this.bootstrap) - .then(() => { - this.refreshTokenTimer(); - }) - .catch(err => { - if (err.message !== 'timeout' && err.code && err.code !== 400) { - console.log(err); - console.error(err.message); - console.info('Not logged in?'); - Controller.showLogin(); - } else { - this.refreshTokenTimer(); - } - }); - }, 10 * 60 * 1000); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/app/nginx/access/delete.ejs b/frontend/js/app/nginx/access/delete.ejs deleted file mode 100644 index 3833549a6..000000000 --- a/frontend/js/app/nginx/access/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/delete.js b/frontend/js/app/nginx/access/delete.js deleted file mode 100644 index 4af91ab17..000000000 --- a/frontend/js/app/nginx/access/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.AccessLists.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxAccess(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/access/form.ejs b/frontend/js/app/nginx/access/form.ejs deleted file mode 100644 index fdd82ae98..000000000 --- a/frontend/js/app/nginx/access/form.ejs +++ /dev/null @@ -1,108 +0,0 @@ - diff --git a/frontend/js/app/nginx/access/form.js b/frontend/js/app/nginx/access/form.js deleted file mode 100644 index 4c2a0c444..000000000 --- a/frontend/js/app/nginx/access/form.js +++ /dev/null @@ -1,153 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const template = require('./form.ejs'); -const ItemView = require('./form/item'); -const ClientView = require('./form/client'); - -require('jquery-serializejson'); - -const ItemsView = Mn.CollectionView.extend({ - childView: ItemView -}); - -const ClientsView = Mn.CollectionView.extend({ - childView: ClientView -}); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - items_region: '.items', - clients_region: '.clients', - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - access_add: 'button.access_add', - auth_add: 'button.auth_add' - }, - - regions: { - items_region: '@ui.items_region', - clients_region: '@ui.clients_region' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let form_data = this.ui.form.serializeJSON(); - let items_data = []; - let clients_data = []; - - form_data.username.map(function (val, idx) { - if (val.trim().length) { - items_data.push({ - username: val.trim(), - password: form_data.password[idx] - }); - } - }); - - form_data.address.map(function (val, idx) { - if (val.trim().length) { - clients_data.push({ - address: val.trim(), - directive: form_data.directive[idx] - }) - } - }); - - if (!items_data.length && !clients_data.length) { - alert('You must specify at least 1 Authorization or Access rule'); - return; - } - - let data = { - name: form_data.name, - satisfy_any: !!form_data.satisfy_any, - pass_auth: !!form_data.pass_auth, - items: items_data, - clients: clients_data - }; - - console.log(data); - - let method = App.Api.Nginx.AccessLists.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.AccessLists.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxAccess(); - } - }); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - }, - 'click @ui.access_add': function (e) { - e.preventDefault(); - - let clients = this.model.get('clients'); - clients.push({}); - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - 'click @ui.auth_add': function (e) { - e.preventDefault(); - - let items = this.model.get('items'); - items.push({}); - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - } - }, - - onRender: function () { - let items = this.model.get('items'); - let clients = this.model.get('clients'); - - // Ensure at least one field is shown initially - if (!items.length) items.push({}); - if (!clients.length) clients.push({}); - - this.showChildView('items_region', new ItemsView({ - collection: new Backbone.Collection(items) - })); - - this.showChildView('clients_region', new ClientsView({ - collection: new Backbone.Collection(clients) - })); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new AccessListModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/access/form/client.ejs b/frontend/js/app/nginx/access/form/client.ejs deleted file mode 100644 index 6b767b83f..000000000 --- a/frontend/js/app/nginx/access/form/client.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/client.js b/frontend/js/app/nginx/access/form/client.js deleted file mode 100644 index b4c00e2e3..000000000 --- a/frontend/js/app/nginx/access/form/client.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./client.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/form/item.ejs b/frontend/js/app/nginx/access/form/item.ejs deleted file mode 100644 index c2435ecb3..000000000 --- a/frontend/js/app/nginx/access/form/item.ejs +++ /dev/null @@ -1,10 +0,0 @@ -
-
- -
-
-
-
- -
-
diff --git a/frontend/js/app/nginx/access/form/item.js b/frontend/js/app/nginx/access/form/item.js deleted file mode 100644 index f15238dcc..000000000 --- a/frontend/js/app/nginx/access/form/item.js +++ /dev/null @@ -1,7 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'row' -}); diff --git a/frontend/js/app/nginx/access/list/item.ejs b/frontend/js/app/nginx/access/list/item.ejs deleted file mode 100644 index 2ee37a50a..000000000 --- a/frontend/js/app/nginx/access/list/item.ejs +++ /dev/null @@ -1,42 +0,0 @@ - -
- -
- - -
- <%- name %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> - - - <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> - - - <% if (satisfy_any) { %> - <%- i18n('str', 'any') %> - <%} else { %> - <%- i18n('str', 'all') %> - <% } %> - - - <%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/access/list/item.js b/frontend/js/app/nginx/access/list/item.js deleted file mode 100644 index 4f68aead7..000000000 --- a/frontend/js/app/nginx/access/list/item.js +++ /dev/null @@ -1,33 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/access/list/main.ejs b/frontend/js/app/nginx/access/list/main.ejs deleted file mode 100644 index 7988e0c28..000000000 --- a/frontend/js/app/nginx/access/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('access-lists', 'authorization') %> - <%- i18n('access-lists', 'access') %> - <%- i18n('access-lists', 'satisfy') %> - <%- i18n('proxy-hosts', 'title') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/access/list/main.js b/frontend/js/app/nginx/access/list/main.js deleted file mode 100644 index 577a77ef2..000000000 --- a/frontend/js/app/nginx/access/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/access/main.ejs b/frontend/js/app/nginx/access/main.ejs deleted file mode 100644 index 975859364..000000000 --- a/frontend/js/app/nginx/access/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('access-lists', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('access-lists', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/access/main.js b/frontend/js/app/nginx/access/main.js deleted file mode 100644 index 513f58659..000000000 --- a/frontend/js/app/nginx/access/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const AccessListModel = require('../../../models/access-list'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-access', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.AccessLists.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new AccessListModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxAccess(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('access_lists'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('access-lists', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('access-lists', 'add') : null, - btn_color: 'teal', - permission: 'access_lists', - action: function () { - App.Controller.showNginxAccessListForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxAccessListForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'items', 'clients'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('access_lists') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'items', 'clients']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates-list-item.ejs b/frontend/js/app/nginx/certificates-list-item.ejs deleted file mode 100644 index aa4b53ad2..000000000 --- a/frontend/js/app/nginx/certificates-list-item.ejs +++ /dev/null @@ -1,18 +0,0 @@ -
- <% if (id === 'new') { %> -
- <%- i18n('all-hosts', 'new-cert') %> -
- <%- i18n('all-hosts', 'with-le') %> - <% } else if (id > 0) { %> -
- <%- provider === 'other' ? nice_name : domain_names.join(', ') %> -
- <%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('all-hosts', 'none') %> -
- <%- i18n('all-hosts', 'no-ssl') %> - <% } %> -
diff --git a/frontend/js/app/nginx/certificates/delete.ejs b/frontend/js/app/nginx/certificates/delete.ejs deleted file mode 100644 index b4e068660..000000000 --- a/frontend/js/app/nginx/certificates/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/delete.js b/frontend/js/app/nginx/certificates/delete.js deleted file mode 100644 index 89a2e5e83..000000000 --- a/frontend/js/app/nginx/certificates/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.save.addClass('btn-loading'); - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Nginx.Certificates.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxCertificates(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/form.ejs b/frontend/js/app/nginx/certificates/form.ejs deleted file mode 100644 index 899fd9701..000000000 --- a/frontend/js/app/nginx/certificates/form.ejs +++ /dev/null @@ -1,184 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/form.js b/frontend/js/app/nginx/certificates/form.js deleted file mode 100644 index f743f2186..000000000 --- a/frontend/js/app/nginx/certificates/form.js +++ /dev/null @@ -1,297 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const template = require('./form.ejs'); -const i18n = require('../../i18n'); -const dns_providers = sortProvidersAlphabetically(require('../../../../certbot-dns-plugins')); - -require('jquery-serializejson'); -require('selectize'); - -function sortProvidersAlphabetically(obj) { - return Object.entries(obj) - .sort((a,b) => a[1].name.toLowerCase() > b[1].name.toLowerCase()) - .reduce((result, entry) => { - result[entry[0]] = entry[1]; - return result; - }, {}); -} - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - max_file_size: 102400, - - ui: { - form: 'form', - loader_content: '.loader-content', - non_loader_content: '.non-loader-content', - le_error_info: '#le-error-info', - domain_names: 'input[name="domain_names"]', - test_domains_container: '.test-domains-container', - test_domains_button: '.test-domains', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - other_certificate: '#other_certificate', - other_certificate_label: '#other_certificate_label', - other_certificate_key: '#other_certificate_key', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - other_certificate_key_label: '#other_certificate_key_label', - other_intermediate_certificate: '#other_intermediate_certificate', - other_intermediate_certificate_label: '#other_intermediate_certificate_label' - }, - - events: { - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - this.ui.test_domains_container.hide(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - this.ui.test_domains_container.show(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - $(this).removeClass('btn-loading'); - return; - } - - let data = this.ui.form.serializeJSON(); - data.provider = this.model.get('provider'); - let ssl_files = []; - - if (data.provider === 'letsencrypt') { - if (typeof data.meta === 'undefined') data.meta = {}; - - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.split(',').map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - - // Manipulate - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - } else if (data.provider === 'other' && !this.model.hasSslFiles()) { - // check files are attached - if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { - alert('Certificate file is not attached'); - return; - } else { - if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { - alert('Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); - } - - if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { - alert('Certificate key file is not attached'); - return; - } else { - if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { - alert('Certificate key file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); - } - - if (!this.ui.other_intermediate_certificate[0].files.length || !this.ui.other_intermediate_certificate[0].files[0].size) { - alert('Intermediate Certificate file is not attached'); - return; - } else { - if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { - alert('Intermediate Certificate file is too large (> 100kb)'); - return; - } - ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); - } - } - - this.ui.loader_content.show(); - this.ui.non_loader_content.hide(); - - // compile file data - let form_data = new FormData(); - if (data.provider === 'other' && ssl_files.length) { - ssl_files.map(function (file) { - form_data.append(file.name, file.file); - }); - } - - new Promise(resolve => { - if (data.provider === 'other') { - resolve(App.Api.Nginx.Certificates.validate(form_data)); - } else { - resolve(); - } - }) - .then(() => { - return App.Api.Nginx.Certificates.create(data); - }) - .then(result => { - this.model.set(result); - - // Now upload the certs if we need to - if (data.provider === 'other') { - return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data) - .then(result => { - this.model.set('meta', _.assign({}, this.model.get('meta'), result)); - }); - } - }) - .then(() => { - App.UI.closeModal(function () { - App.Controller.showNginxCertificates(); - }); - }) - .catch(err => { - let more_info = ''; - if (err.code === 500 && err.debug) { - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.loader_content.hide(); - this.ui.non_loader_content.show(); - }); - }, - 'click @ui.test_domains_button': function (e) { - e.preventDefault(); - const domainNames = this.ui.domain_names[0].value.split(','); - if (domainNames && domainNames.length > 0) { - this.model.set('domain_names', domainNames); - this.model.set('back_to_add', true); - App.Controller.showNginxCertificateTestReachability(this.model); - } - }, - 'change @ui.domain_names': function(e){ - const domainNames = e.target.value.split(','); - if (domainNames && domainNames.length > 0) { - this.ui.test_domains_button.prop('disabled', false); - } else { - this.ui.test_domains_button.prop('disabled', true); - } - }, - 'change @ui.other_certificate_key': function(e){ - this.setFileName("other_certificate_key_label", e) - }, - 'change @ui.other_certificate': function(e){ - this.setFileName("other_certificate_label", e) - }, - 'change @ui.other_intermediate_certificate': function(e){ - this.setFileName("other_intermediate_certificate_label", e) - } - }, - setFileName(ui, e){ - this.getUI(ui).text(e.target.files[0].name) - }, - templateContext: { - getLetsencryptEmail: function () { - return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); - }, - getLetsencryptAgree: function () { - return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 99, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.loader_content.hide(); - this.ui.le_error_info.hide(); - if (this.ui.domain_names[0]) { - const domainNames = this.ui.domain_names[0].value.split(','); - if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) { - this.ui.test_domains_button.prop('disabled', true); - } - } - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new CertificateModel.Model({provider: 'letsencrypt'}); - } - } -}); diff --git a/frontend/js/app/nginx/certificates/list/item.ejs b/frontend/js/app/nginx/certificates/list/item.ejs deleted file mode 100644 index 20d6f2396..000000000 --- a/frontend/js/app/nginx/certificates/list/item.ejs +++ /dev/null @@ -1,54 +0,0 @@ - -
- -
- - -
- <% - if (provider === 'letsencrypt') { - domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - } else { - %><%- nice_name %><% - } - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - - <%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].name %><% } %> - - - <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/certificates/list/item.js b/frontend/js/app/nginx/certificates/list/item.js deleted file mode 100644 index db273e90f..000000000 --- a/frontend/js/app/nginx/certificates/list/item.js +++ /dev/null @@ -1,58 +0,0 @@ -const Mn = require('backbone.marionette'); -const moment = require('moment'); -const App = require('../../../main'); -const template = require('./item.ejs'); -const dns_providers = require('../../../../../certbot-dns-plugins'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - host_link: '.host-link', - renew: 'a.renew', - delete: 'a.delete', - download: 'a.download', - test: 'a.test' - }, - - events: { - 'click @ui.renew': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateRenew(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - }, - - 'click @ui.download': function (e) { - e.preventDefault(); - App.Api.Nginx.Certificates.download(this.model.get('id')); - }, - - 'click @ui.test': function (e) { - e.preventDefault(); - App.Controller.showNginxCertificateTestReachability(this.model); - }, - }, - - templateContext: { - canManage: App.Cache.User.canManage('certificates'), - isExpired: function () { - return moment(this.expires_on).isBefore(moment()); - }, - dns_providers: dns_providers - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/certificates/list/main.ejs b/frontend/js/app/nginx/certificates/list/main.ejs deleted file mode 100644 index aa49a27fb..000000000 --- a/frontend/js/app/nginx/certificates/list/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('all-hosts', 'cert-provider') %> - <%- i18n('str', 'expires') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/certificates/list/main.js b/frontend/js/app/nginx/certificates/list/main.js deleted file mode 100644 index d96b43e8f..000000000 --- a/frontend/js/app/nginx/certificates/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/certificates/main.ejs b/frontend/js/app/nginx/certificates/main.ejs deleted file mode 100644 index dbd6fa85d..000000000 --- a/frontend/js/app/nginx/certificates/main.ejs +++ /dev/null @@ -1,36 +0,0 @@ -
-
-
-

<%- i18n('certificates', 'title') %>

-
- - - <% if (showAddButton) { %> - - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/certificates/main.js b/frontend/js/app/nginx/certificates/main.js deleted file mode 100644 index 89562768b..000000000 --- a/frontend/js/app/nginx/certificates/main.js +++ /dev/null @@ -1,109 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const CertificateModel = require('../../../models/certificate'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-certificates', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Certificates.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new CertificateModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxCertificates(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('certificates'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('certificates', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('certificates', 'add') : null, - btn_color: 'pink', - permission: 'certificates', - action: function () { - App.Controller.showNginxCertificateForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - let model = new CertificateModel.Model({provider: $(e.currentTarget).data('cert')}); - App.Controller.showNginxCertificateForm(model); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('certificates', 'help-title'), App.i18n('certificates', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('certificates') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/renew.ejs b/frontend/js/app/nginx/certificates/renew.ejs deleted file mode 100644 index 4af186d09..000000000 --- a/frontend/js/app/nginx/certificates/renew.ejs +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/renew.js b/frontend/js/app/nginx/certificates/renew.js deleted file mode 100644 index 736328817..000000000 --- a/frontend/js/app/nginx/certificates/renew.js +++ /dev/null @@ -1,31 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./renew.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - close: 'button.cancel' - }, - - onRender: function () { - this.ui.error.hide(); - - App.Api.Nginx.Certificates.renew(this.model.get('id')) - .then((result) => { - this.model.set(result); - setTimeout(() => { - App.UI.closeModal(); - }, 1000); - }) - .catch((err) => { - this.ui.waiting.hide(); - this.ui.error.text(err.message).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/certificates/test.ejs b/frontend/js/app/nginx/certificates/test.ejs deleted file mode 100644 index 6661f625f..000000000 --- a/frontend/js/app/nginx/certificates/test.ejs +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/frontend/js/app/nginx/certificates/test.js b/frontend/js/app/nginx/certificates/test.js deleted file mode 100644 index 0886d26f5..000000000 --- a/frontend/js/app/nginx/certificates/test.js +++ /dev/null @@ -1,75 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./test.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - waiting: '.waiting', - error: '.error', - success: '.success', - close: 'button.cancel' - }, - - events: { - 'click @ui.close': function (e) { - e.preventDefault(); - if (this.model.get('back_to_add')) { - App.Controller.showNginxCertificateForm(this.model); - } else { - App.UI.closeModal(); - } - }, - }, - - onRender: function () { - this.ui.error.hide(); - this.ui.success.hide(); - - App.Api.Nginx.Certificates.testHttpChallenge(this.model.get('domain_names')) - .then((result) => { - let allOk = true; - let text = ''; - - for (const domain in result) { - const status = result[domain]; - if (status === 'ok') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-ok')}

`; - } else { - allOk = false; - if (status === 'no-host') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-not-resolved')}

`; - } else if (status === 'failed') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-failed-to-check')}

`; - } else if (status === '404') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-404')}

`; - } else if (status === 'wrong-data') { - text += `

${domain}: ${App.i18n('certificates', 'reachability-wrong-data')}

`; - } else if (status.startsWith('other:')) { - const code = status.substring(6); - text += `

${domain}: ${App.i18n('certificates', 'reachability-other', {code})}

`; - } else { - // This should never happen - text += `

${domain}: ?

`; - } - } - } - - this.ui.waiting.hide(); - if (allOk) { - this.ui.success.html(text).show(); - } else { - this.ui.error.html(text).show(); - } - this.ui.close.prop('disabled', false); - }) - .catch((e) => { - console.error(e); - this.ui.waiting.hide(); - this.ui.error.text(App.i18n('certificates', 'reachability-failed-to-reach-api')).show(); - this.ui.close.prop('disabled', false); - }); - } -}); diff --git a/frontend/js/app/nginx/dead/delete.ejs b/frontend/js/app/nginx/dead/delete.ejs deleted file mode 100644 index 4bebb4360..000000000 --- a/frontend/js/app/nginx/dead/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/delete.js b/frontend/js/app/nginx/dead/delete.js deleted file mode 100644 index d497d0685..000000000 --- a/frontend/js/app/nginx/dead/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.DeadHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxDead(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/dead/form.ejs b/frontend/js/app/nginx/dead/form.ejs deleted file mode 100644 index addc2b213..000000000 --- a/frontend/js/app/nginx/dead/form.ejs +++ /dev/null @@ -1,206 +0,0 @@ - diff --git a/frontend/js/app/nginx/dead/form.js b/frontend/js/app/nginx/dead/form.js deleted file mode 100644 index e0c4bb765..000000000 --- a/frontend/js/app/nginx/dead/form.js +++ /dev/null @@ -1,274 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../certbot-dns-plugins'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.hsts_subdomains); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.http2_support = !!data.http2_support; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.DeadHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.DeadHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxDead(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 99, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new DeadHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/dead/list/item.ejs b/frontend/js/app/nginx/dead/list/item.ejs deleted file mode 100644 index d447bd1e3..000000000 --- a/frontend/js/app/nginx/dead/list/item.ejs +++ /dev/null @@ -1,54 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/dead/list/item.js b/frontend/js/app/nginx/dead/list/item.js deleted file mode 100644 index a477dbfa3..000000000 --- a/frontend/js/app/nginx/dead/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.DeadHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.DeadHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/dead/list/main.ejs b/frontend/js/app/nginx/dead/list/main.ejs deleted file mode 100644 index e018a74b0..000000000 --- a/frontend/js/app/nginx/dead/list/main.ejs +++ /dev/null @@ -1,12 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/dead/list/main.js b/frontend/js/app/nginx/dead/list/main.js deleted file mode 100644 index 579314199..000000000 --- a/frontend/js/app/nginx/dead/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/dead/main.ejs b/frontend/js/app/nginx/dead/main.ejs deleted file mode 100644 index 4c5d1ad1f..000000000 --- a/frontend/js/app/nginx/dead/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('dead-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('dead-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/dead/main.js b/frontend/js/app/nginx/dead/main.js deleted file mode 100644 index e4d0c010e..000000000 --- a/frontend/js/app/nginx/dead/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const DeadHostModel = require('../../../models/dead-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-dead', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.DeadHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new DeadHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxDead(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('dead_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('dead-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('dead-hosts', 'add') : null, - btn_color: 'danger', - permission: 'dead_hosts', - action: function () { - App.Controller.showNginxDeadForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxDeadForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('dead_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/proxy/access-list-item.ejs b/frontend/js/app/nginx/proxy/access-list-item.ejs deleted file mode 100644 index e5a7e1163..000000000 --- a/frontend/js/app/nginx/proxy/access-list-item.ejs +++ /dev/null @@ -1,13 +0,0 @@ -
- <% if (id > 0) { %> -
- <%- name %> -
- <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> - <% } else { %> -
- <%- i18n('access-lists', 'public') %> -
- <%- i18n('access-lists', 'public-sub') %> - <% } %> -
diff --git a/frontend/js/app/nginx/proxy/delete.ejs b/frontend/js/app/nginx/proxy/delete.ejs deleted file mode 100644 index 74da297c2..000000000 --- a/frontend/js/app/nginx/proxy/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/delete.js b/frontend/js/app/nginx/proxy/delete.js deleted file mode 100644 index 63a8e020a..000000000 --- a/frontend/js/app/nginx/proxy/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxProxy(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/form.ejs b/frontend/js/app/nginx/proxy/form.ejs deleted file mode 100644 index 74fec07d7..000000000 --- a/frontend/js/app/nginx/proxy/form.ejs +++ /dev/null @@ -1,281 +0,0 @@ - diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js deleted file mode 100644 index 45d785830..000000000 --- a/frontend/js/app/nginx/proxy/form.js +++ /dev/null @@ -1,357 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ProxyLocationModel = require('../../../models/proxy-host-location'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const accessListItemTemplate = require('./access-list-item.ejs'); -const CustomLocation = require('./location'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - locationsCollection: new ProxyLocationModel.Collection(), - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - forward_host: 'input[name="forward_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - add_location_btn: 'button.add_location', - locations_container: '.locations_container', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - access_list_select: 'select[name="access_list_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - forward_scheme: 'select[name="forward_scheme"]', - letsencrypt: '.letsencrypt' - }, - - regions: { - locations_regions: '@ui.locations_container' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.hsts_subdomains); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.add_location_btn': function (e) { - e.preventDefault(); - - const model = new ProxyLocationModel.Model(); - this.locationsCollection.add(model); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Add locations - data.locations = []; - this.locationsCollection.models.forEach((location) => { - data.locations.push(location.toJSON()); - }); - - // Serialize collects path from custom locations - // This field must be removed from root object - delete data.path; - - // Manipulate - data.forward_port = parseInt(data.forward_port, 10); - data.block_exploits = !!data.block_exploits; - data.caching_enabled = !!data.caching_enabled; - data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.ProxyHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.ProxyHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxProxy(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - this.ui.ssl_forced.trigger('change'); - this.ui.hsts_enabled.trigger('change'); - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 99, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Access Lists - this.ui.access_list_select.selectize({ - valueField: 'id', - labelField: 'name', - searchField: ['name'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return accessListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.AccessLists.getAll(['items', 'clients']) - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); - } - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new ProxyHostModel.Model(); - } - - this.locationsCollection = new ProxyLocationModel.Collection(); - - // Custom locations - this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ - collection: this.locationsCollection - })); - - // Check whether there are any location defined - if (options.model && Array.isArray(options.model.attributes.locations)) { - options.model.attributes.locations.forEach((location) => { - let m = new ProxyLocationModel.Model(location); - this.locationsCollection.add(m); - }); - } - } -}); diff --git a/frontend/js/app/nginx/proxy/list/item.ejs b/frontend/js/app/nginx/proxy/list/item.ejs deleted file mode 100644 index a59368048..000000000 --- a/frontend/js/app/nginx/proxy/list/item.ejs +++ /dev/null @@ -1,60 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_scheme %>://<%- forward_host %>:<%- forward_port %>
- - -
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - -
<%- access_list_id ? access_list.name : i18n('str', 'public') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/list/item.js b/frontend/js/app/nginx/proxy/list/item.js deleted file mode 100644 index 37d199b4a..000000000 --- a/frontend/js/app/nginx/proxy/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.ProxyHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/proxy/list/main.ejs b/frontend/js/app/nginx/proxy/list/main.ejs deleted file mode 100644 index 6de5b9c68..000000000 --- a/frontend/js/app/nginx/proxy/list/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'access') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/proxy/list/main.js b/frontend/js/app/nginx/proxy/list/main.js deleted file mode 100644 index 09e984e62..000000000 --- a/frontend/js/app/nginx/proxy/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/proxy/location-item.ejs b/frontend/js/app/nginx/proxy/location-item.ejs deleted file mode 100644 index 6124c8a1b..000000000 --- a/frontend/js/app/nginx/proxy/location-item.ejs +++ /dev/null @@ -1,64 +0,0 @@ -
-
-
-
-
- -
-
-
- - location - - -
-
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
- - - <%- i18n('proxy-hosts', 'custom-forward-host-help') %> -
-
-
-
- - -
-
-
-
-
-
- -
-
-
- - - <%- i18n('locations', 'delete') %> - -
-
diff --git a/frontend/js/app/nginx/proxy/location.js b/frontend/js/app/nginx/proxy/location.js deleted file mode 100644 index e9513a480..000000000 --- a/frontend/js/app/nginx/proxy/location.js +++ /dev/null @@ -1,54 +0,0 @@ -const locationItemTemplate = require('./location-item.ejs'); -const Mn = require('backbone.marionette'); -const App = require('../../main'); - -const LocationView = Mn.View.extend({ - template: locationItemTemplate, - className: 'location_block', - - ui: { - toggle: 'input[type="checkbox"]', - config: '.config', - delete: '.location-delete' - }, - - events: { - 'change @ui.toggle': function(el) { - if (el.target.checked) { - this.ui.config.show(); - } else { - this.ui.config.hide(); - } - }, - - 'change .model': function (e) { - const map = {}; - map[e.target.name] = e.target.value; - this.model.set(map); - }, - - 'click @ui.delete': function () { - this.model.destroy(); - } - }, - - onRender: function() { - $(this.ui.config).hide(); - }, - - templateContext: function() { - return { - i18n: App.i18n - } - } -}); - -const LocationCollectionView = Mn.CollectionView.extend({ - className: 'locations_container', - childView: LocationView -}); - -module.exports = { - LocationCollectionView, - LocationView -} \ No newline at end of file diff --git a/frontend/js/app/nginx/proxy/main.ejs b/frontend/js/app/nginx/proxy/main.ejs deleted file mode 100644 index 4ecb9036a..000000000 --- a/frontend/js/app/nginx/proxy/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('proxy-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('proxy-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/proxy/main.js b/frontend/js/app/nginx/proxy/main.js deleted file mode 100644 index baf671013..000000000 --- a/frontend/js/app/nginx/proxy/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-proxy', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.ProxyHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new ProxyHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxProxy(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('proxy_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('proxy-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('proxy-hosts', 'add') : null, - btn_color: 'success', - permission: 'proxy_hosts', - action: function () { - App.Controller.showNginxProxyForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxProxyForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'access_list', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'access_list', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/redirection/delete.ejs b/frontend/js/app/nginx/redirection/delete.ejs deleted file mode 100644 index 782d8435d..000000000 --- a/frontend/js/app/nginx/redirection/delete.ejs +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/delete.js b/frontend/js/app/nginx/redirection/delete.js deleted file mode 100644 index 6d2862f68..000000000 --- a/frontend/js/app/nginx/redirection/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxRedirection(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/form.ejs b/frontend/js/app/nginx/redirection/form.ejs deleted file mode 100644 index f3d689e2d..000000000 --- a/frontend/js/app/nginx/redirection/form.ejs +++ /dev/null @@ -1,255 +0,0 @@ - diff --git a/frontend/js/app/nginx/redirection/form.js b/frontend/js/app/nginx/redirection/form.js deleted file mode 100644 index ad6779192..000000000 --- a/frontend/js/app/nginx/redirection/form.js +++ /dev/null @@ -1,276 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../certbot-dns-plugins'); - - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - letsencrypt: '.letsencrypt' - }, - - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } - - let enabled = id === 'new' || parseInt(id, 10) > 0; - - let inputs = this.ui.ssl_forced.add(this.ui.hsts_subdomains); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); - - if (!enabled) { - inputs.prop('checked', false); - } - - inputs.trigger('change'); - }, - - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); - - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } - - this.ui.hsts_enabled.trigger('change'); - }, - - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, - - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - data.block_exploits = !!data.block_exploits; - data.preserve_path = !!data.preserve_path; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } - - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } - - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } - - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } - - let method = App.Api.Nginx.RedirectionHosts.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.RedirectionHosts.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); - - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxRedirection(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}
`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, - - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, - - onRender: function () { - let view = this; - - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 99, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ - }); - - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new RedirectionHostModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/redirection/list/item.ejs b/frontend/js/app/nginx/redirection/list/item.ejs deleted file mode 100644 index 4f25d973e..000000000 --- a/frontend/js/app/nginx/redirection/list/item.ejs +++ /dev/null @@ -1,63 +0,0 @@ - -
- -
- - -
- <% domain_names.map(function(host) { - if (host.indexOf('*') === -1) { - %> - <%- host %> - <% - } else { - %> - <%- host %> - <% - } - }); - %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forward_http_code %>
- - -
<%- forward_scheme == '$scheme' ? 'auto' : forward_scheme %>
- - -
<%- forward_domain_name %>
- - -
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> diff --git a/frontend/js/app/nginx/redirection/list/item.js b/frontend/js/app/nginx/redirection/list/item.js deleted file mode 100644 index 05adc2511..000000000 --- a/frontend/js/app/nginx/redirection/list/item.js +++ /dev/null @@ -1,61 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete', - host_link: '.host-link' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.RedirectionHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.RedirectionHosts.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionDeleteConfirm(this.model); - }, - - 'click @ui.host_link': function (e) { - e.preventDefault(); - let win = window.open($(e.currentTarget).attr('rel'), '_blank'); - win.focus(); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/redirection/list/main.ejs b/frontend/js/app/nginx/redirection/list/main.ejs deleted file mode 100644 index 8b6930d60..000000000 --- a/frontend/js/app/nginx/redirection/list/main.ejs +++ /dev/null @@ -1,15 +0,0 @@ - -   - <%- i18n('str', 'source') %> - <%- i18n('redirection-hosts', 'forward-http-status-code') %> - <%- i18n('redirection-hosts', 'forward-scheme') %> - <%- i18n('str', 'destination') %> - <%- i18n('str', 'ssl') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/redirection/list/main.js b/frontend/js/app/nginx/redirection/list/main.js deleted file mode 100644 index d368cf6aa..000000000 --- a/frontend/js/app/nginx/redirection/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('redirection_hosts') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/redirection/main.ejs b/frontend/js/app/nginx/redirection/main.ejs deleted file mode 100644 index 87e28229a..000000000 --- a/frontend/js/app/nginx/redirection/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('redirection-hosts', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('redirection-hosts', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/redirection/main.js b/frontend/js/app/nginx/redirection/main.js deleted file mode 100644 index 1f5351a73..000000000 --- a/frontend/js/app/nginx/redirection/main.js +++ /dev/null @@ -1,107 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const RedirectionHostModel = require('../../../models/redirection-host'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-redirection', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.RedirectionHosts.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new RedirectionHostModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxRedirection(); - } - })); - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('redirection_hosts'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('redirection-hosts', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('redirection-hosts', 'add') : null, - btn_color: 'yellow', - permission: 'redirection_hosts', - action: function () { - App.Controller.showNginxRedirectionForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxRedirectionForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('redirection-hosts', 'help-title'), App.i18n('redirection-hosts', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner', 'certificate'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('proxy_hosts') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner', 'certificate']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/nginx/stream/delete.ejs b/frontend/js/app/nginx/stream/delete.ejs deleted file mode 100644 index d7ba3a217..000000000 --- a/frontend/js/app/nginx/stream/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/delete.js b/frontend/js/app/nginx/stream/delete.js deleted file mode 100644 index 71eff18cc..000000000 --- a/frontend/js/app/nginx/stream/delete.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./delete.ejs'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Nginx.Streams.delete(this.model.get('id')) - .then(() => { - App.Controller.showNginxStream(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/nginx/stream/form.ejs b/frontend/js/app/nginx/stream/form.ejs deleted file mode 100644 index 1fc4f1342..000000000 --- a/frontend/js/app/nginx/stream/form.ejs +++ /dev/null @@ -1,55 +0,0 @@ - diff --git a/frontend/js/app/nginx/stream/form.js b/frontend/js/app/nginx/stream/form.js deleted file mode 100644 index be8fc8bc2..000000000 --- a/frontend/js/app/nginx/stream/form.js +++ /dev/null @@ -1,84 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const template = require('./form.ejs'); - -require('jquery-serializejson'); -require('jquery-mask-plugin'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - forwarding_host: 'input[name="forwarding_host"]', - type_error: '.forward-type-error', - buttons: '.modal-footer button', - switches: '.custom-switch-input', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - 'change @ui.switches': function () { - this.ui.type_error.hide(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - - if (!data.tcp_forwarding && !data.udp_forwarding) { - this.ui.type_error.show(); - return; - } - - // Manipulate - data.incoming_port = parseInt(data.incoming_port, 10); - data.forwarding_port = parseInt(data.forwarding_port, 10); - data.tcp_forwarding = !!data.tcp_forwarding; - data.udp_forwarding = !!data.udp_forwarding; - - let method = App.Api.Nginx.Streams.create; - let is_new = true; - - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.Streams.update; - data.id = this.model.get('id'); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - method(data) - .then(result => { - view.model.set(result); - - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxStream(); - } - }); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new StreamModel.Model(); - } - } -}); diff --git a/frontend/js/app/nginx/stream/list/item.ejs b/frontend/js/app/nginx/stream/list/item.ejs deleted file mode 100644 index a8ff83d4c..000000000 --- a/frontend/js/app/nginx/stream/list/item.ejs +++ /dev/null @@ -1,53 +0,0 @@ - -
- -
- - -
- <%- incoming_port %> -
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- forwarding_host %>:<%- forwarding_port %>
- - -
- <% if (tcp_forwarding) { %> - <%- i18n('streams', 'tcp') %> - <% } - if (udp_forwarding) { %> - <%- i18n('streams', 'udp') %> - <% } %> -
- - - <% - var o = isOnline(); - if (!enabled) { %> - <%- i18n('str', 'disabled') %> - <% } else if (o === true) { %> - <%- i18n('str', 'online') %> - <% } else if (o === false) { %> - <%- i18n('str', 'offline') %> - <% } else { %> - <%- i18n('str', 'unknown') %> - <% } %> - -<% if (canManage) { %> - - - -<% } %> \ No newline at end of file diff --git a/frontend/js/app/nginx/stream/list/item.js b/frontend/js/app/nginx/stream/list/item.js deleted file mode 100644 index a6892ee2a..000000000 --- a/frontend/js/app/nginx/stream/list/item.js +++ /dev/null @@ -1,54 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - able: 'a.able', - edit: 'a.edit', - delete: 'a.delete' - }, - - events: { - 'click @ui.able': function (e) { - e.preventDefault(); - let id = this.model.get('id'); - App.Api.Nginx.Streams[this.model.get('enabled') ? 'disable' : 'enable'](id) - .then(() => { - return App.Api.Nginx.Streams.get(id) - .then(row => { - this.model.set(row); - }); - }); - }, - - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamDeleteConfirm(this.model); - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams'), - - isOnline: function () { - return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; - }, - - getOfflineError: function () { - return this.meta.nginx_err || ''; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/nginx/stream/list/main.ejs b/frontend/js/app/nginx/stream/list/main.ejs deleted file mode 100644 index 5304f6145..000000000 --- a/frontend/js/app/nginx/stream/list/main.ejs +++ /dev/null @@ -1,13 +0,0 @@ - -   - <%- i18n('streams', 'incoming-port') %> - <%- i18n('str', 'destination') %> - <%- i18n('streams', 'protocol') %> - <%- i18n('str', 'status') %> - <% if (canManage) { %> -   - <% } %> - - - - diff --git a/frontend/js/app/nginx/stream/list/main.js b/frontend/js/app/nginx/stream/list/main.js deleted file mode 100644 index 36be621d9..000000000 --- a/frontend/js/app/nginx/stream/list/main.js +++ /dev/null @@ -1,32 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../../main'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - templateContext: { - canManage: App.Cache.User.canManage('streams') - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/nginx/stream/main.ejs b/frontend/js/app/nginx/stream/main.ejs deleted file mode 100644 index 7dc0dbe86..000000000 --- a/frontend/js/app/nginx/stream/main.ejs +++ /dev/null @@ -1,28 +0,0 @@ -
-
-
-

<%- i18n('streams', 'title') %>

-
- - - <% if (showAddButton) { %> - <%- i18n('streams', 'add') %> - <% } %> -
-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/nginx/stream/main.js b/frontend/js/app/nginx/stream/main.js deleted file mode 100644 index 8a86e5836..000000000 --- a/frontend/js/app/nginx/stream/main.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const StreamModel = require('../../../models/stream'); -const ListView = require('./list/main'); -const ErrorView = require('../../error/main'); -const EmptyView = require('../../empty/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'nginx-stream', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Nginx.Streams.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new StreamModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showNginxStream(); - } - })); - - console.error(err); - }, - - showEmpty: function() { - let manage = App.Cache.User.canManage('streams'); - - this.showChildView('list_region', new EmptyView({ - title: App.i18n('streams', 'empty'), - subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('streams', 'add') : null, - btn_color: 'blue', - permission: 'streams', - action: function () { - App.Controller.showNginxStreamForm(); - } - })); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showNginxStreamForm(); - }, - - 'click @ui.help': function (e) { - e.preventDefault(); - App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content')); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['owner'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - templateContext: { - showAddButton: App.Cache.User.canManage('streams') - }, - - onRender: function () { - let view = this; - - view.fetch(['owner']) - .then(response => { - if (!view.isDestroyed()) { - if (response && response.length) { - view.showData(response); - } else { - view.showEmpty(); - } - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/router.js b/frontend/js/app/router.js deleted file mode 100644 index a036bfc57..000000000 --- a/frontend/js/app/router.js +++ /dev/null @@ -1,19 +0,0 @@ -const AppRouter = require('marionette.approuter'); -const Controller = require('./controller'); - -module.exports = AppRouter.default.extend({ - controller: Controller, - appRoutes: { - users: 'showUsers', - logout: 'logout', - 'nginx/proxy': 'showNginxProxy', - 'nginx/redirection': 'showNginxRedirection', - 'nginx/404': 'showNginxDead', - 'nginx/stream': 'showNginxStream', - 'nginx/access': 'showNginxAccess', - 'nginx/certificates': 'showNginxCertificates', - 'audit-log': 'showAuditLog', - 'settings': 'showSettings', - '*default': 'showDashboard' - } -}); diff --git a/frontend/js/app/settings/default-site/main.ejs b/frontend/js/app/settings/default-site/main.ejs deleted file mode 100644 index d74ac0bd3..000000000 --- a/frontend/js/app/settings/default-site/main.ejs +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/frontend/js/app/settings/default-site/main.js b/frontend/js/app/settings/default-site/main.js deleted file mode 100644 index 06a45b8bc..000000000 --- a/frontend/js/app/settings/default-site/main.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./main.ejs'); - -require('jquery-serializejson'); -require('selectize'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - options: '.option-item', - value: 'input[name="value"]', - redirect: '.redirect-input', - html: '.html-content' - }, - - events: { - 'change @ui.value': function (e) { - let val = this.ui.value.filter(':checked').val(); - this.ui.options.hide(); - this.ui.options.filter('.option-' + val).show(); - }, - - 'click @ui.save': function (e) { - e.preventDefault(); - - let val = this.ui.value.filter(':checked').val(); - - // Clear redirect field before validation - if (val !== 'redirect') { - this.ui.redirect.val('').attr('required', false); - } else { - this.ui.redirect.attr('required', true); - } - - this.ui.html.attr('required', val === 'html'); - - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } - - let view = this; - let data = this.ui.form.serializeJSON(); - data.id = this.model.get('id'); - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Settings.update(data) - .then(result => { - view.model.set(result); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - onRender: function () { - this.ui.value.trigger('change'); - } -}); diff --git a/frontend/js/app/settings/list/item.ejs b/frontend/js/app/settings/list/item.ejs deleted file mode 100644 index 1623c4dc7..000000000 --- a/frontend/js/app/settings/list/item.ejs +++ /dev/null @@ -1,21 +0,0 @@ - -
<%- i18n('settings', 'default-site') %>
-
- <%- i18n('settings', 'default-site-description') %> -
- - -
- <% if (id === 'default-site') { %> - <%- i18n('settings', 'default-site-' + value) %> - <% } %> -
- - - - \ No newline at end of file diff --git a/frontend/js/app/settings/list/item.js b/frontend/js/app/settings/list/item.js deleted file mode 100644 index 03f9ac05b..000000000 --- a/frontend/js/app/settings/list/item.js +++ /dev/null @@ -1,23 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showSettingForm(this.model); - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/settings/list/main.ejs b/frontend/js/app/settings/list/main.ejs deleted file mode 100644 index c96e923a5..000000000 --- a/frontend/js/app/settings/list/main.ejs +++ /dev/null @@ -1,8 +0,0 @@ - - <%- i18n('str', 'name') %> - <%- i18n('str', 'value') %> -   - - - - diff --git a/frontend/js/app/settings/list/main.js b/frontend/js/app/settings/list/main.js deleted file mode 100644 index 9d3e26fb1..000000000 --- a/frontend/js/app/settings/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/settings/main.ejs b/frontend/js/app/settings/main.ejs deleted file mode 100644 index 2b02769f2..000000000 --- a/frontend/js/app/settings/main.ejs +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
-

<%- i18n('settings', 'title') %>

-
-
-
-
-
- -
-
-
-
diff --git a/frontend/js/app/settings/main.js b/frontend/js/app/settings/main.js deleted file mode 100644 index 96b2941ff..000000000 --- a/frontend/js/app/settings/main.js +++ /dev/null @@ -1,48 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const SettingModel = require('../../models/setting'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'settings', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer' - }, - - regions: { - list_region: '@ui.list_region' - }, - - onRender: function () { - let view = this; - - App.Api.Settings.getAll() - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showChildView('list_region', new ListView({ - collection: new SettingModel.Collection(response) - })); - } - }) - .catch(err => { - view.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showSettings(); - } - })); - - console.error(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/app/tokens.js b/frontend/js/app/tokens.js deleted file mode 100644 index 4a56bcab6..000000000 --- a/frontend/js/app/tokens.js +++ /dev/null @@ -1,126 +0,0 @@ -const STORAGE_NAME = 'nginx-proxy-manager-tokens'; - -/** - * @returns {Array} - */ -const getStorageTokens = function () { - let json = window.localStorage.getItem(STORAGE_NAME); - if (json) { - try { - return JSON.parse(json); - } catch (err) { - return []; - } - } - - return []; -}; - -/** - * @param {Array} tokens - */ -const setStorageTokens = function (tokens) { - window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); -}; - -const Tokens = { - - /** - * @returns {Number} - */ - getTokenCount: () => { - return getStorageTokens().length; - }, - - /** - * @returns {Object} t,n - */ - getTopToken: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length) { - return tokens[0]; - } - - return null; - }, - - /** - * @returns {String} - */ - getNextTokenName: () => { - let tokens = getStorageTokens(); - if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { - return tokens[1].n; - } - - return null; - }, - - /** - * - * @param {String} token - * @param {String} [name] - * @returns {Number} - */ - addToken: (token, name) => { - // Get top token and if it's the same, ignore this call - let top = Tokens.getTopToken(); - if (!top || top.t !== token) { - let tokens = getStorageTokens(); - tokens.unshift({t: token, n: name || null}); - setStorageTokens(tokens); - } - - return Tokens.getTokenCount(); - }, - - /** - * @param {String} token - * @returns {Boolean} - */ - setCurrentToken: token => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].t = token; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @param {String} name - * @returns {Boolean} - */ - setCurrentName: name => { - let tokens = getStorageTokens(); - if (tokens.length) { - tokens[0].n = name; - setStorageTokens(tokens); - return true; - } - - return false; - }, - - /** - * @returns {Number} - */ - dropTopToken: () => { - let tokens = getStorageTokens(); - tokens.shift(); - setStorageTokens(tokens); - return tokens.length; - }, - - /** - * - */ - clearTokens: () => { - window.localStorage.removeItem(STORAGE_NAME); - } - -}; - -module.exports = Tokens; diff --git a/frontend/js/app/ui/footer/main.ejs b/frontend/js/app/ui/footer/main.ejs deleted file mode 100644 index eaa57d99b..000000000 --- a/frontend/js/app/ui/footer/main.ejs +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- <%- i18n('main', 'version', {version: getVersion()}) %> - <%= i18n('footer', 'copy', {url: 'https://jc21.com'}) %> - <%= i18n('footer', 'copyzv', {url: 'https://zoeyvid.de'}) %> - <%= i18n('footer', 'theme', {url: 'https://tabler.github.io'}) %> -
-
diff --git a/frontend/js/app/ui/footer/main.js b/frontend/js/app/ui/footer/main.js deleted file mode 100644 index 73f515e68..000000000 --- a/frontend/js/app/ui/footer/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const Cache = require('../../cache'); - -module.exports = Mn.View.extend({ - className: 'container', - template: template, - - templateContext: { - getVersion: function () { - return Cache.version || '0.0.0'; - } - } -}); diff --git a/frontend/js/app/ui/header/main.ejs b/frontend/js/app/ui/header/main.ejs deleted file mode 100644 index 18ed2b6a6..000000000 --- a/frontend/js/app/ui/header/main.ejs +++ /dev/null @@ -1,34 +0,0 @@ - diff --git a/frontend/js/app/ui/header/main.js b/frontend/js/app/ui/header/main.js deleted file mode 100644 index 9779b45c3..000000000 --- a/frontend/js/app/ui/header/main.js +++ /dev/null @@ -1,67 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const i18n = require('../../i18n'); -const Cache = require('../../cache'); -const Controller = require('../../controller'); -const Tokens = require('../../tokens'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'header', - className: 'header', - template: template, - - ui: { - link: 'a', - details: 'a.edit-details', - password: 'a.change-password' - }, - - events: { - 'click @ui.details': function (e) { - e.preventDefault(); - Controller.showUserForm(Cache.User); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - Controller.showUserPasswordForm(Cache.User); - }, - - 'click @ui.link': function (e) { - e.preventDefault(); - let href = $(e.currentTarget).attr('href'); - - switch (href) { - case '/': - Controller.showDashboard(); - break; - case '/logout': - Controller.logout(); - break; - } - } - }, - - templateContext: { - getUserField: function (field, default_val) { - return Cache.User.get(field) || default_val; - }, - - getRole: function () { - return i18n('roles', Cache.User.isAdmin() ? 'admin' : 'user'); - }, - - getLogoutText: function () { - if (Tokens.getTokenCount() > 1) { - return i18n('main', 'sign-in-as', {name: Tokens.getNextTokenName()}); - } - - return i18n('str', 'sign-out'); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/ui/main.ejs b/frontend/js/app/ui/main.ejs deleted file mode 100644 index b62c3acda..000000000 --- a/frontend/js/app/ui/main.ejs +++ /dev/null @@ -1,21 +0,0 @@ -
- -
-
- -
-
-
- -
- -
- - diff --git a/frontend/js/app/ui/main.js b/frontend/js/app/ui/main.js deleted file mode 100644 index c90c61d5d..000000000 --- a/frontend/js/app/ui/main.js +++ /dev/null @@ -1,98 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./main.ejs'); -const HeaderView = require('./header/main'); -const MenuView = require('./menu/main'); -const FooterView = require('./footer/main'); -const Cache = require('../cache'); - -module.exports = Mn.View.extend({ - id: 'app', - className: 'page', - template: template, - modal_setup: false, - - modal: null, - - ui: { - modal: '#modal-dialog' - }, - - regions: { - header_region: { - el: '#header', - replaceElement: true - }, - menu_region: { - el: '#menu', - replaceElement: true - }, - footer_region: '.footer', - app_content_region: '#app-content', - modal_region: '#modal-dialog' - }, - - /** - * @param {Object} view - */ - showAppContent: function (view) { - this.showChildView('app_content_region', view); - }, - - /** - * @param {Object} view - * @param {Function} [show_callback] - * @param {Function} [shown_callback] - */ - showModalDialog: function (view, show_callback, shown_callback) { - this.showChildView('modal_region', view); - let modal = this.getRegion('modal_region').$el.modal('show'); - - modal.on('hidden.bs.modal', function (/*e*/) { - if (show_callback) { - modal.off('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.off('shown.bs.modal', shown_callback); - } - - modal.off('hidden.bs.modal'); - view.destroy(); - }); - - if (show_callback) { - modal.on('show.bs.modal', show_callback); - } - - if (shown_callback) { - modal.on('shown.bs.modal', shown_callback); - } - }, - - /** - * - * @param {Function} [hidden_callback] - */ - closeModal: function (hidden_callback) { - let modal = this.getRegion('modal_region').$el.modal('hide'); - - if (hidden_callback) { - modal.on('hidden.bs.modal', hidden_callback); - } - }, - - onRender: function () { - this.showChildView('header_region', new HeaderView({ - model: Cache.User - })); - - this.showChildView('menu_region', new MenuView()); - this.showChildView('footer_region', new FooterView()); - }, - - reset: function () { - this.getRegion('header_region').reset(); - this.getRegion('footer_region').reset(); - this.getRegion('modal_region').reset(); - } -}); diff --git a/frontend/js/app/ui/menu/main.ejs b/frontend/js/app/ui/menu/main.ejs deleted file mode 100644 index 671b4e3be..000000000 --- a/frontend/js/app/ui/menu/main.ejs +++ /dev/null @@ -1,52 +0,0 @@ -
-
-
- -
-
-
diff --git a/frontend/js/app/ui/menu/main.js b/frontend/js/app/ui/menu/main.js deleted file mode 100644 index dabe26d36..000000000 --- a/frontend/js/app/ui/menu/main.js +++ /dev/null @@ -1,39 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const Controller = require('../../controller'); -const Cache = require('../../cache'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'menu', - className: 'header collapse d-lg-flex p-0', - template: template, - - ui: { - links: 'a' - }, - - events: { - 'click @ui.links': function (e) { - let href = $(e.currentTarget).attr('href'); - if (href !== '#') { - e.preventDefault(); - Controller.navigate(href, true); - } - } - }, - - templateContext: { - isAdmin: function () { - return Cache.User.isAdmin(); - }, - - canShow: function (perm) { - return Cache.User.isAdmin() || Cache.User.canView(perm); - } - }, - - initialize: function () { - this.listenTo(Cache.User, 'change', this.render); - } -}); diff --git a/frontend/js/app/user/delete.ejs b/frontend/js/app/user/delete.ejs deleted file mode 100644 index c10532ef8..000000000 --- a/frontend/js/app/user/delete.ejs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/frontend/js/app/user/delete.js b/frontend/js/app/user/delete.js deleted file mode 100644 index e8ed5c32e..000000000 --- a/frontend/js/app/user/delete.js +++ /dev/null @@ -1,34 +0,0 @@ -const Mn = require('backbone.marionette'); -const template = require('./delete.ejs'); -const App = require('../main'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - App.Api.Users.delete(this.model.get('id')) - .then(() => { - App.Controller.showUsers(); - App.UI.closeModal(); - }) - .catch(err => { - alert(err.message); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } -}); diff --git a/frontend/js/app/user/form.ejs b/frontend/js/app/user/form.ejs deleted file mode 100644 index aeb268f7c..000000000 --- a/frontend/js/app/user/form.ejs +++ /dev/null @@ -1,58 +0,0 @@ - diff --git a/frontend/js/app/user/form.js b/frontend/js/app/user/form.js deleted file mode 100644 index ef92ec3e9..000000000 --- a/frontend/js/app/user/form.js +++ /dev/null @@ -1,108 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./form.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.error.hide(); - let view = this; - let data = this.ui.form.serializeJSON(); - - let show_password = this.model.get('email') === 'admin@example.com'; - - // admin@example.com is not allowed - if (data.email === 'admin@example.com') { - this.ui.error.text(App.i18n('users', 'default_error')).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - return; - } - - // Manipulate - data.roles = []; - if ((this.model.get('id') === App.Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { - data.roles.push('admin'); - delete data.is_admin; - } - - data.is_disabled = typeof data.is_disabled !== 'undefined' ? !!data.is_disabled : false; - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - let method = App.Api.Users.create; - - if (this.model.get('id')) { - // edit - method = App.Api.Users.update; - data.id = this.model.get('id'); - } - - method(data) - .then(result => { - if (result.id === App.Cache.User.get('id')) { - App.Cache.User.set(result); - } - - if (view.model.get('id') !== App.Cache.User.get('id')) { - App.Controller.showUsers(); - } - - view.model.set(result); - App.UI.closeModal(function () { - if (method === App.Api.Users.create) { - // Show permissions dialog immediately - App.Controller.showUserPermissions(view.model); - } else if (show_password) { - App.Controller.showUserPasswordForm(view.model); - } - }); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let view = this; - - return { - isSelf: function () { - return view.model.get('id') === App.Cache.User.get('id'); - }, - - isAdmin: function () { - return App.Cache.User.isAdmin(); - }, - - isAdminUser: function () { - return view.model.isAdmin(); - }, - - isDisabled: function () { - return view.model.isDisabled(); - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/user/password.ejs b/frontend/js/app/user/password.ejs deleted file mode 100644 index a45cc7ed7..000000000 --- a/frontend/js/app/user/password.ejs +++ /dev/null @@ -1,31 +0,0 @@ - diff --git a/frontend/js/app/user/password.js b/frontend/js/app/user/password.js deleted file mode 100644 index 840307502..000000000 --- a/frontend/js/app/user/password.js +++ /dev/null @@ -1,69 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const template = require('./password.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - newSecretError: '.new-secret-error', - generalError: '#error-info', - }, - - events: { - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - let form = this.ui.form.serializeJSON(); - - if (form.new_password1 !== form.new_password2) { - this.ui.newSecretError.text('Passwords do not match!').show(); - return; - } - - let data = { - type: 'password', - current: form.current_password, - secret: form.new_password1 - }; - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - App.Api.Users.setPassword(this.model.get('id'), data) - .then(() => { - App.UI.closeModal(); - App.Controller.showUsers(); - }) - .catch(err => { - // Change error message to make it a little clearer - if (err.message === 'Invalid password') { - err.message = 'Current password is invalid'; - } - this.ui.generalError.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - isSelf: function () { - return App.Cache.User.get('id') === this.model.get('id'); - }, - - templateContext: function () { - return { - isSelf: this.isSelf.bind(this) - }; - }, - - onRender: function () { - this.ui.newSecretError.hide(); - this.ui.generalError.hide(); - }, -}); diff --git a/frontend/js/app/user/permissions.ejs b/frontend/js/app/user/permissions.ejs deleted file mode 100644 index b61617960..000000000 --- a/frontend/js/app/user/permissions.ejs +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/frontend/js/app/user/permissions.js b/frontend/js/app/user/permissions.js deleted file mode 100644 index af8049ce8..000000000 --- a/frontend/js/app/user/permissions.js +++ /dev/null @@ -1,95 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const template = require('./permissions.ejs'); - -require('jquery-serializejson'); - -module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', - - ui: { - form: 'form', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - error: '.secret-error' - }, - - events: { - - 'click @ui.save': function (e) { - e.preventDefault(); - - let view = this; - let data = this.ui.form.serializeJSON(); - - // Manipulate - if (view.model.isAdmin()) { - // Force some attributes for admin - data = _.assign({}, data, { - access_lists: 'manage', - dead_hosts: 'manage', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - streams: 'manage', - certificates: 'manage' - }); - } - - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.setPermissions(view.model.get('id'), data) - .then(() => { - if (view.model.get('id') === App.Cache.User.get('id')) { - App.Cache.User.set({permissions: data}); - } - - view.model.set({permissions: data}); - App.UI.closeModal(); - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - }); - } - }, - - templateContext: function () { - let perms = this.model.get('permissions'); - let is_admin = this.model.isAdmin(); - - return { - getPerm: function (key) { - if (perms !== null && typeof perms[key] !== 'undefined') { - return perms[key]; - } - - return null; - }, - - getPermProps: function (key, item, forced_admin) { - if (forced_admin && is_admin) { - return 'checked disabled'; - } else if (is_admin) { - return 'disabled'; - } else if (perms !== null && typeof perms[key] !== 'undefined' && perms[key] === item) { - return 'checked'; - } - - return ''; - }, - - isAdmin: function () { - return is_admin; - } - }; - }, - - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new UserModel.Model(); - } - } -}); diff --git a/frontend/js/app/users/list/item.ejs b/frontend/js/app/users/list/item.ejs deleted file mode 100644 index fab5585bc..000000000 --- a/frontend/js/app/users/list/item.ejs +++ /dev/null @@ -1,45 +0,0 @@ - -
- -
- - -
<%- name %>
-
- <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> -
- - -
<%- email %>
- - -
- <% - var r = []; - roles.map(function(role) { - if (role) { - r.push(i18n('roles', role)); - } - }); - %> - <%- r.join(', ') %> -
- - - - diff --git a/frontend/js/app/users/list/item.js b/frontend/js/app/users/list/item.js deleted file mode 100644 index 4645a5c46..000000000 --- a/frontend/js/app/users/list/item.js +++ /dev/null @@ -1,68 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const Tokens = require('../../tokens'); -const template = require('./item.ejs'); - -module.exports = Mn.View.extend({ - template: template, - tagName: 'tr', - - ui: { - edit: 'a.edit-user', - permissions: 'a.edit-permissions', - password: 'a.set-password', - login: 'a.login', - delete: 'a.delete-user' - }, - - events: { - 'click @ui.edit': function (e) { - e.preventDefault(); - App.Controller.showUserForm(this.model); - }, - - 'click @ui.permissions': function (e) { - e.preventDefault(); - App.Controller.showUserPermissions(this.model); - }, - - 'click @ui.password': function (e) { - e.preventDefault(); - App.Controller.showUserPasswordForm(this.model); - }, - - 'click @ui.delete': function (e) { - e.preventDefault(); - App.Controller.showUserDeleteConfirm(this.model); - }, - - 'click @ui.login': function (e) { - e.preventDefault(); - - if (App.Cache.User.get('id') !== this.model.get('id')) { - this.ui.login.prop('disabled', true).addClass('btn-disabled'); - - App.Api.Users.loginAs(this.model.get('id')) - .then(res => { - Tokens.addToken(res.token, res.user.nickname || res.user.name); - window.location = '/'; - window.location.reload(); - }) - .catch(err => { - alert(err.message); - this.ui.login.prop('disabled', false).removeClass('btn-disabled'); - }); - } - } - }, - - templateContext: { - isSelf: function () { - return App.Cache.User.get('id') === this.id; - } - }, - - initialize: function () { - this.listenTo(this.model, 'change', this.render); - } -}); diff --git a/frontend/js/app/users/list/main.ejs b/frontend/js/app/users/list/main.ejs deleted file mode 100644 index c85c9cb1c..000000000 --- a/frontend/js/app/users/list/main.ejs +++ /dev/null @@ -1,10 +0,0 @@ - -   - <%- i18n('str', 'name') %> - <%- i18n('str', 'email') %> - <%- i18n('str', 'roles') %> -   - - - - diff --git a/frontend/js/app/users/list/main.js b/frontend/js/app/users/list/main.js deleted file mode 100644 index 9d3e26fb1..000000000 --- a/frontend/js/app/users/list/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const Mn = require('backbone.marionette'); -const ItemView = require('./item'); -const template = require('./main.ejs'); - -const TableBody = Mn.CollectionView.extend({ - tagName: 'tbody', - childView: ItemView -}); - -module.exports = Mn.View.extend({ - tagName: 'table', - className: 'table table-hover table-outline table-vcenter card-table', - template: template, - - regions: { - body: { - el: 'tbody', - replaceElement: true - } - }, - - onRender: function () { - this.showChildView('body', new TableBody({ - collection: this.collection - })); - } -}); diff --git a/frontend/js/app/users/main.ejs b/frontend/js/app/users/main.ejs deleted file mode 100644 index 892cb83f6..000000000 --- a/frontend/js/app/users/main.ejs +++ /dev/null @@ -1,26 +0,0 @@ -
-
-
-

<%- i18n('users', 'title') %>

-
- - <%- i18n('users', 'add') %> -
-
-
-
-
-
- -
-
- -
-
diff --git a/frontend/js/app/users/main.js b/frontend/js/app/users/main.js deleted file mode 100644 index 42cb41ef1..000000000 --- a/frontend/js/app/users/main.js +++ /dev/null @@ -1,78 +0,0 @@ -const Mn = require('backbone.marionette'); -const App = require('../main'); -const UserModel = require('../../models/user'); -const ListView = require('./list/main'); -const ErrorView = require('../error/main'); -const template = require('./main.ejs'); - -module.exports = Mn.View.extend({ - id: 'users', - template: template, - - ui: { - list_region: '.list-region', - add: '.add-item', - dimmer: '.dimmer', - search: '.search-form', - query: 'input[name="source-query"]' - }, - - fetch: App.Api.Users.getAll, - - showData: function(response) { - this.showChildView('list_region', new ListView({ - collection: new UserModel.Collection(response) - })); - }, - - showError: function(err) { - this.showChildView('list_region', new ErrorView({ - code: err.code, - message: err.message, - retry: function () { - App.Controller.showUsers(); - } - })); - - console.error(err); - }, - - regions: { - list_region: '@ui.list_region' - }, - - events: { - 'click @ui.add': function (e) { - e.preventDefault(); - App.Controller.showUserForm(new UserModel.Model()); - }, - - 'submit @ui.search': function (e) { - e.preventDefault(); - let query = this.ui.query.val(); - - this.fetch(['permissions'], query) - .then(response => this.showData(response)) - .catch(err => { - this.showError(err); - }); - } - }, - - onRender: function () { - let view = this; - - view.fetch(['permissions']) - .then(response => { - if (!view.isDestroyed() && response && response.length) { - view.showData(response); - } - }) - .catch(err => { - view.showError(err); - }) - .then(() => { - view.ui.dimmer.removeClass('active'); - }); - } -}); diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json deleted file mode 100644 index 874b6085c..000000000 --- a/frontend/js/i18n/messages.json +++ /dev/null @@ -1,297 +0,0 @@ -{ - "en": { - "str": { - "email-address": "Email address", - "username": "Username", - "password": "Password", - "sign-in": "Sign in", - "sign-out": "Sign out", - "try-again": "Try again", - "name": "Name", - "email": "Email", - "roles": "Roles", - "created-on": "Created: {date}", - "save": "Save", - "cancel": "Cancel", - "close": "Close", - "enable": "Enable", - "disable": "Disable", - "sure": "Yes I'm Sure", - "disabled": "Disabled", - "choose-file": "Choose file", - "source": "Source", - "destination": "Destination", - "ssl": "TLS", - "access": "Access", - "public": "Public", - "edit": "Edit", - "delete": "Delete", - "logs": "Logs", - "status": "Status", - "online": "Online", - "offline": "Offline", - "unknown": "Unknown", - "expires": "Expires", - "value": "Value", - "please-wait": "Please wait...", - "all": "All", - "any": "Any" - }, - "login": { - "title": "Login to your account" - }, - "main": { - "app": "NPMplus", - "version": "0.0.0", - "welcome": "Welcome to NPMplus", - "logged-in": "You are logged in as {name}", - "unknown-error": "Error loading stuff. Please reload the app.", - "unknown-user": "Unknown User", - "sign-in-as": "Sign back in as {name}" - }, - "roles": { - "title": "Roles", - "admin": "Administrator", - "user": "User" - }, - "menu": { - "dashboard": "Dashboard", - "hosts": "Hosts" - }, - "footer": { - "fork-me": "Repository on GitHub", - "copy": "© 2024 jc21.com NPM", - "copyzv": "and © 2024 ZoeyVid NPMplus - MIT-License - ", - "theme": "Theme by Tabler v0.0.31" - }, - "dashboard": { - "title": "Hi {name}" - }, - "all-hosts": { - "empty-subtitle": "{manage, select, true{Why don't you create one?} other{And you don't have permission to create one.}}", - "details": "Details", - "enable-ssl": "Enable HTTPS", - "force-ssl": "Force HTTPS", - "http2-support": "Enable Brotli", - "domain-names": "Domain Names", - "cert-provider": "Certificate Provider", - "block-exploits": "Enable ModSecurity", - "caching-enabled": "Enable CoreRuleSet (Requires ModSecurity)", - "ssl-certificate": "TLS Certificate", - "none": "None", - "new-cert": "Request a new TLS Certificate", - "with-le": "with Certbot", - "no-ssl": "This host will not use HTTPS", - "advanced": "Advanced", - "advanced-warning": "Enter your custom Nginx configuration here at your own risk!", - "advanced-config": "Custom Nginx Configuration", - "advanced-config-var-headline": "These proxy details are available as nginx variables:", - "advanced-config-header-info": "Please note, adding a location '/' will overwrite the proxy configuration", - "hsts-enabled": "Enable HSTS and security headers", - "hsts-subdomains": "Enable HTTP/3-Quic", - "locations": "Custom locations" - }, - "locations": { - "new_location": "Add location", - "path": "/path", - "location_label": "Define location", - "delete": "Delete" - }, - "ssl": { - "letsencrypt": "Certbot", - "other": "Custom", - "none": "HTTP only", - "letsencrypt-email": "Email Address for Certbot", - "letsencrypt-agree": "I Agree to the Let's Encrypt Terms of Service / ToS of custom set CA", - "delete-ssl": "The TLS certificates attached will NOT be removed, they will need to be removed manually.", - "hosts-warning": "These domains must be already configured to point to this installation", - "no-wildcard-without-dns": "Cannot request Certificate for wildcard domains when not using DNS challenge", - "dns-challenge": "Use a DNS Challenge", - "certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.", - "dns-provider": "DNS Provider", - "please-choose": "Please Choose...", - "credentials-file-content": "Credentials File Content", - "credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider", - "stored-as-plaintext-info": "This data will be stored as plaintext in the database and in a file!", - "propagation-seconds": "Propagation Seconds", - "propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.", - "processing-info": "Processing... This might take a few minutes.", - "passphrase-protection-support-info": "Key files protected with a passphrase are not supported." - }, - "proxy-hosts": { - "title": "Proxy Hosts", - "empty": "There are no Proxy Hosts", - "add": "Add Proxy Host", - "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", - "forward-scheme": "Scheme", - "forward-host": "Forward Hostname / IP", - "forward-port": "Forward Port", - "delete": "Delete Proxy Host", - "delete-confirm": "Are you sure you want to delete the Proxy host for: {domains}?", - "help-title": "What is a Proxy Host?", - "help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional TLS termination for your service that might not have TLS support built in.\nProxy Hosts are the most common use for the NPMplus.", - "access-list": "Access List", - "allow-websocket-upgrade": "Websockets Support", - "ignore-invalid-upstream-ssl": "Ignore Invalid TLS", - "custom-forward-host-help": "Add a path for sub-folder forwarding.\nExample: 203.0.113.25/path/", - "search": "Search Host…" - }, - "redirection-hosts": { - "title": "Redirection Hosts", - "empty": "There are no Redirection Hosts", - "add": "Add Redirection Host", - "form-title": "{id, select, undefined{New} other{Edit}} Redirection Host", - "forward-scheme": "Scheme", - "forward-http-status-code": "HTTP Code", - "forward-domain": "Forward Domain", - "preserve-path": "Preserve Path", - "delete": "Delete Redirection Host", - "delete-confirm": "Are you sure you want to delete the Redirection host for: {domains}?", - "help-title": "What is a Redirection Host?", - "help-content": "A Redirection Host will redirect requests from the incoming domain and push the viewer to another domain.\nThe most common reason to use this type of host is when your website changes domains but you still have search engine or referrer links pointing to the old domain.", - "search": "Search Host…" - }, - "dead-hosts": { - "title": "404 Hosts", - "empty": "There are no 404 Hosts", - "add": "Add 404 Host", - "form-title": "{id, select, undefined{New} other{Edit}} 404 Host", - "delete": "Delete 404 Host", - "delete-confirm": "Are you sure you want to delete this 404 Host?", - "help-title": "What is a 404 Host?", - "help-content": "A 404 Host is simply a host setup that shows a 404 page.\nThis can be useful when your domain is listed in search engines and you want to provide a nicer error page or specifically to tell the search indexers that the domain pages no longer exist.\nAnother benefit of having this host is to track the logs for hits to it and view the referrers.", - "search": "Search Host…" - }, - "streams": { - "title": "Streams", - "empty": "There are no Streams", - "add": "Add Stream", - "form-title": "{id, select, undefined{New} other{Edit}} Stream", - "incoming-port": "Incoming Port", - "forwarding-host": "Forward Host", - "forwarding-port": "Forward Port", - "tcp-forwarding": "TCP Forwarding", - "udp-forwarding": "UDP Forwarding", - "forward-type-error": "At least one type of protocol must be enabled", - "protocol": "Protocol", - "tcp": "TCP", - "udp": "UDP", - "delete": "Delete Stream", - "delete-confirm": "Are you sure you want to delete this Stream?", - "help-title": "What is a Stream?", - "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", - "search": "Search Incoming Port…" - }, - "certificates": { - "title": "TLS Certificates", - "empty": "There are no TLS Certificates", - "add": "Add TLS Certificate", - "form-title": "Add {provider, select, letsencrypt{Certbot} other{Custom}} Certificate", - "delete": "Delete TLS Certificate", - "delete-confirm": "Are you sure you want to delete this TLS Certificate? Any hosts using it will need to be updated later.", - "help-title": "TLS Certificates", - "help-content": "TLS certificates (previously known as SSL Certificates) are a form of encryption key which allows your site to be encrypted for the end user.\nNPM uses by default a service called Let's Encrypt to issue TLS certificates for free.\nIf you have any sort of personal information, passwords, or sensitive data behind NPM, it's probably a good idea to use a certificate.\nNPM also supports DNS authentication for if you're not running your site facing the internet, or if you just want a wildcard certificate.", - "other-certificate": "Certificate", - "other-certificate-key": "Certificate Key", - "other-intermediate-certificate": "Intermediate Certificate", - "force-renew": "Renew Now", - "test-reachability": "Test Server Reachability", - "reachability-title": "Test Server Reachability", - "reachability-info": "Test whether the domains are reachable from the public internet using Site24x7. This is not necessary when using the DNS Challenge.", - "reachability-failed-to-reach-api": "Communication with the API failed, is NPM running correctly?", - "reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.", - "reachability-ok": "Your server is reachable and creating certificates should be possible.", - "reachability-404": "There is a server found at this domain but it does not seem to be NPMplus. Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.", - "reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", - "download": "Download", - "renew-title": "Renew Certificate", - "search": "Search Certificate…" - }, - "access-lists": { - "title": "Access Lists", - "empty": "There are no Access Lists", - "add": "Add Access List", - "form-title": "{id, select, undefined{New} other{Edit}} Access List", - "delete": "Delete Access List", - "delete-confirm": "Are you sure you want to delete this access list?", - "public": "Publicly Accessible", - "public-sub": "No Access Restrictions", - "help-title": "What is an Access List?", - "help-content": "Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple client rules, usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in or that you want to protect from access by unknown clients.", - "item-count": "{count} {count, select, 1{User} other{Users}}", - "client-count": "{count} {count, select, 1{Rule} other{Rules}}", - "proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}", - "delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion.", - "details": "Details", - "authorization": "Authorization", - "access": "Access", - "satisfy": "Satisfy", - "satisfy-any": "Allow access if at least one authorization method succeeded", - "pass-auth": "Don't pass credentials to backend of host", - "access-add": "Add", - "auth-add": "Add", - "search": "Search Access…" - }, - "users": { - "title": "Users", - "default_error": "Default email address must be changed", - "add": "Add User", - "nickname": "Nickname", - "full-name": "Full Name", - "edit-details": "Edit Details", - "change-password": "Change Password", - "edit-permissions": "Edit Permissions", - "sign-in-as": "Sign in as User", - "form-title": "{id, select, undefined{New} other{Edit}} User", - "delete": "Delete {name, select, undefined{User} other{{name}}}", - "delete-confirm": "Are you sure you want to delete {name}?", - "password-title": "Change Password{self, select, false{ for {name}} other{}}", - "current-password": "Current Password", - "new-password": "New Password", - "confirm-password": "Confirm Password", - "permissions-title": "Permissions for {name}", - "admin-perms": "This user is an Administrator and some items cannot be altered", - "perms-visibility": "Item Visibility", - "perms-visibility-user": "Created Items Only", - "perms-visibility-all": "All Items", - "perm-manage": "Manage", - "perm-view": "View Only", - "perm-hidden": "Hidden", - "search": "Search User…" - }, - "audit-log": { - "title": "Audit Log", - "empty": "There are no logs.", - "empty-subtitle": "As soon as you or another user changes something, history of those events will show up here.", - "proxy-host": "Proxy Host", - "redirection-host": "Redirection Host", - "dead-host": "404 Host", - "stream": "Stream", - "user": "User", - "certificate": "Certificate", - "access-list": "Access List", - "created": "Created {name}", - "updated": "Updated {name}", - "deleted": "Deleted {name}", - "enabled": "Enabled {name}", - "disabled": "Disabled {name}", - "renewed": "Renewed {name}", - "meta-title": "Details for Event", - "view-meta": "View Details", - "date": "Date", - "search": "Search Log…" - }, - "settings": { - "title": "Settings", - "default-site": "Default Site", - "default-site-description": "What to show when Nginx is hit with an unknown Host", - "default-site-congratulations": "Congratulations Page", - "default-site-404": "404 Page", - "default-site-444": "Drop connection - only allows certbot dns-challenge", - "default-site-html": "Custom Page", - "default-site-redirect": "Redirect" - } - } -} diff --git a/frontend/js/index.js b/frontend/js/index.js deleted file mode 100644 index 3d817d71d..000000000 --- a/frontend/js/index.js +++ /dev/null @@ -1,119 +0,0 @@ -// This has to exist here so that Webpack picks it up -import '../scss/styles.scss'; - -window.tabler = { - colors: { - 'blue': '#467fcf', - 'blue-darkest': '#0e1929', - 'blue-darker': '#1c3353', - 'blue-dark': '#3866a6', - 'blue-light': '#7ea5dd', - 'blue-lighter': '#c8d9f1', - 'blue-lightest': '#edf2fa', - 'azure': '#45aaf2', - 'azure-darkest': '#0e2230', - 'azure-darker': '#1c4461', - 'azure-dark': '#3788c2', - 'azure-light': '#7dc4f6', - 'azure-lighter': '#c7e6fb', - 'azure-lightest': '#ecf7fe', - 'indigo': '#6574cd', - 'indigo-darkest': '#141729', - 'indigo-darker': '#282e52', - 'indigo-dark': '#515da4', - 'indigo-light': '#939edc', - 'indigo-lighter': '#d1d5f0', - 'indigo-lightest': '#f0f1fa', - 'purple': '#a55eea', - 'purple-darkest': '#21132f', - 'purple-darker': '#42265e', - 'purple-dark': '#844bbb', - 'purple-light': '#c08ef0', - 'purple-lighter': '#e4cff9', - 'purple-lightest': '#f6effd', - 'pink': '#f66d9b', - 'pink-darkest': '#31161f', - 'pink-darker': '#622c3e', - 'pink-dark': '#c5577c', - 'pink-light': '#f999b9', - 'pink-lighter': '#fcd3e1', - 'pink-lightest': '#fef0f5', - 'red': '#e74c3c', - 'red-darkest': '#2e0f0c', - 'red-darker': '#5c1e18', - 'red-dark': '#b93d30', - 'red-light': '#ee8277', - 'red-lighter': '#f8c9c5', - 'red-lightest': '#fdedec', - 'orange': '#fd9644', - 'orange-darkest': '#331e0e', - 'orange-darker': '#653c1b', - 'orange-dark': '#ca7836', - 'orange-light': '#feb67c', - 'orange-lighter': '#fee0c7', - 'orange-lightest': '#fff5ec', - 'yellow': '#f1c40f', - 'yellow-darkest': '#302703', - 'yellow-darker': '#604e06', - 'yellow-dark': '#c19d0c', - 'yellow-light': '#f5d657', - 'yellow-lighter': '#fbedb7', - 'yellow-lightest': '#fef9e7', - 'lime': '#7bd235', - 'lime-darkest': '#192a0b', - 'lime-darker': '#315415', - 'lime-dark': '#62a82a', - 'lime-light': '#a3e072', - 'lime-lighter': '#d7f2c2', - 'lime-lightest': '#f2fbeb', - 'green': '#5eba00', - 'green-darkest': '#132500', - 'green-darker': '#264a00', - 'green-dark': '#4b9500', - 'green-light': '#8ecf4d', - 'green-lighter': '#cfeab3', - 'green-lightest': '#eff8e6', - 'teal': '#2bcbba', - 'teal-darkest': '#092925', - 'teal-darker': '#11514a', - 'teal-dark': '#22a295', - 'teal-light': '#6bdbcf', - 'teal-lighter': '#bfefea', - 'teal-lightest': '#eafaf8', - 'cyan': '#17a2b8', - 'cyan-darkest': '#052025', - 'cyan-darker': '#09414a', - 'cyan-dark': '#128293', - 'cyan-light': '#5dbecd', - 'cyan-lighter': '#b9e3ea', - 'cyan-lightest': '#e8f6f8', - 'gray': '#868e96', - 'gray-darkest': '#1b1c1e', - 'gray-darker': '#36393c', - 'gray-light': '#aab0b6', - 'gray-lighter': '#dbdde0', - 'gray-lightest': '#f3f4f5', - 'gray-dark': '#343a40', - 'gray-dark-darkest': '#0a0c0d', - 'gray-dark-darker': '#15171a', - 'gray-dark-dark': '#2a2e33', - 'gray-dark-light': '#717579', - 'gray-dark-lighter': '#c2c4c6', - 'gray-dark-lightest': '#ebebec' - } -}; - -String.prototype.toHtmlEntities = function() { - return this.replace(/./gm, function(s) { - // return "&#" + s.charCodeAt(0) + ";"; - return (s.match(/[a-z0-9\s]+/i)) ? s : "&#" + s.charCodeAt(0) + ";"; - }); -}; - -require('tabler-core'); - -const App = require('./app/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/lib/helpers.js b/frontend/js/lib/helpers.js deleted file mode 100644 index 21ce74243..000000000 --- a/frontend/js/lib/helpers.js +++ /dev/null @@ -1,26 +0,0 @@ -const numeral = require('numeral'); -const moment = require('moment'); - -module.exports = { - - /** - * @param {Integer} number - * @returns {String} - */ - niceNumber: function (number) { - return numeral(number).format('0,0'); - }, - - /** - * @param {String|Number} date - * @param {String} format - * @returns {String} - */ - formatDbDate: function (date, format) { - if (typeof date === 'number') { - return moment.unix(date).format(format); - } - - return moment(date).format(format); - } -}; diff --git a/frontend/js/lib/marionette.js b/frontend/js/lib/marionette.js deleted file mode 100644 index c88368f89..000000000 --- a/frontend/js/lib/marionette.js +++ /dev/null @@ -1,15 +0,0 @@ -const _ = require('underscore'); -const Mn = require('backbone.marionette'); -const i18n = require('../app/i18n'); -const Helpers = require('./helpers'); -const TemplateCache = require('marionette.templatecache'); - -Mn.setRenderer(function (template, data, view) { - data = _.clone(data); - data.i18n = i18n; - data.formatDbDate = Helpers.formatDbDate; - - return TemplateCache.default.render.call(this, template, data, view); -}); - -module.exports = Mn; diff --git a/frontend/js/login.js b/frontend/js/login.js deleted file mode 100644 index 0094e2a23..000000000 --- a/frontend/js/login.js +++ /dev/null @@ -1,5 +0,0 @@ -const App = require('./login/main'); - -$(document).ready(() => { - App.start(); -}); diff --git a/frontend/js/login/main.js b/frontend/js/login/main.js deleted file mode 100644 index 03fdc7e56..000000000 --- a/frontend/js/login/main.js +++ /dev/null @@ -1,14 +0,0 @@ -const Mn = require('backbone.marionette'); -const LoginView = require('./ui/login'); - -const App = Mn.Application.extend({ - region: '#login', - UI: null, - - onStart: function (/*app, options*/) { - this.getRegion().show(new LoginView()); - } -}); - -const app = new App(); -module.exports = app; diff --git a/frontend/js/login/ui/login.ejs b/frontend/js/login/ui/login.ejs deleted file mode 100644 index 693bc050c..000000000 --- a/frontend/js/login/ui/login.ejs +++ /dev/null @@ -1,37 +0,0 @@ -
-
- -
-
diff --git a/frontend/js/login/ui/login.js b/frontend/js/login/ui/login.js deleted file mode 100644 index 757eb4e31..000000000 --- a/frontend/js/login/ui/login.js +++ /dev/null @@ -1,42 +0,0 @@ -const $ = require('jquery'); -const Mn = require('backbone.marionette'); -const template = require('./login.ejs'); -const Api = require('../../app/api'); -const i18n = require('../../app/i18n'); - -module.exports = Mn.View.extend({ - template: template, - className: 'page-single', - - ui: { - form: 'form', - identity: 'input[name="identity"]', - secret: 'input[name="secret"]', - error: '.secret-error', - button: 'button' - }, - - events: { - 'submit @ui.form': function (e) { - e.preventDefault(); - this.ui.button.addClass('btn-loading').prop('disabled', true); - this.ui.error.hide(); - - Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true) - .then(() => { - window.location = '/'; - }) - .catch(err => { - this.ui.error.text(err.message).show(); - this.ui.button.removeClass('btn-loading').prop('disabled', false); - }); - } - }, - - templateContext: { - i18n: i18n, - getVersion: function () { - return $('#login').data('version'); - } - } -}); diff --git a/frontend/js/models/access-list.js b/frontend/js/models/access-list.js deleted file mode 100644 index 0c2c4abea..000000000 --- a/frontend/js/models/access-list.js +++ /dev/null @@ -1,25 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - name: '', - items: [], - clients: [], - // The following are expansions: - owner: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/audit-log.js b/frontend/js/models/audit-log.js deleted file mode 100644 index c929a0bd8..000000000 --- a/frontend/js/models/audit-log.js +++ /dev/null @@ -1,18 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - name: '' - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/certificate.js b/frontend/js/models/certificate.js deleted file mode 100644 index c7d0b2d97..000000000 --- a/frontend/js/models/certificate.js +++ /dev/null @@ -1,38 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - provider: '', - nice_name: '', - domain_names: [], - expires_on: null, - meta: {}, - // The following are expansions: - owner: null, - proxy_hosts: [], - redirection_hosts: [], - dead_hosts: [] - }; - }, - - /** - * @returns {Boolean} - */ - hasSslFiles: function () { - let meta = this.get('meta'); - return typeof meta['certificate'] !== 'undefined' && meta['certificate'] && typeof meta['certificate_key'] !== 'undefined' && meta['certificate_key']; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/dead-host.js b/frontend/js/models/dead-host.js deleted file mode 100644 index 98ceef29e..000000000 --- a/frontend/js/models/dead-host.js +++ /dev/null @@ -1,32 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - certificate_id: 0, - ssl_forced: false, - http2_support: false, - hsts_enabled: false, - hsts_subdomains: false, - enabled: true, - meta: {}, - advanced_config: '', - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/proxy-host-location.js b/frontend/js/models/proxy-host-location.js deleted file mode 100644 index 2a35059f8..000000000 --- a/frontend/js/models/proxy-host-location.js +++ /dev/null @@ -1,35 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function() { - return { - opened: false, - path: '', - advanced_config: '', - forward_scheme: 'http', - forward_host: '', - forward_port: '80' - } - }, - - toJSON() { - const r = Object.assign({}, this.attributes); - delete r.opened; - return r; - }, - - toggleVisibility: function () { - this.save({ - opened: !this.get('opened') - }); - } -}) - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model - }) -} \ No newline at end of file diff --git a/frontend/js/models/proxy-host.js b/frontend/js/models/proxy-host.js deleted file mode 100644 index b82d09fef..000000000 --- a/frontend/js/models/proxy-host.js +++ /dev/null @@ -1,40 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_scheme: 'http', - forward_host: '', - forward_port: null, - access_list_id: 0, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - caching_enabled: false, - allow_websocket_upgrade: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - access_list: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/redirection-host.js b/frontend/js/models/redirection-host.js deleted file mode 100644 index 1d0b0de24..000000000 --- a/frontend/js/models/redirection-host.js +++ /dev/null @@ -1,37 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_http_code: 0, - forward_scheme: null, - forward_domain_name: '', - preserve_path: true, - certificate_id: 0, - ssl_forced: false, - hsts_enabled: false, - hsts_subdomains: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - enabled: true, - meta: {}, - // The following are expansions: - owner: null, - certificate: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/setting.js b/frontend/js/models/setting.js deleted file mode 100644 index c70a4e9cb..000000000 --- a/frontend/js/models/setting.js +++ /dev/null @@ -1,22 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - description: '', - value: null, - meta: [] - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/stream.js b/frontend/js/models/stream.js deleted file mode 100644 index ba035429a..000000000 --- a/frontend/js/models/stream.js +++ /dev/null @@ -1,29 +0,0 @@ -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - created_on: null, - modified_on: null, - incoming_port: null, - forwarding_host: null, - forwarding_port: null, - tcp_forwarding: true, - udp_forwarding: false, - enabled: true, - meta: {}, - // The following are expansions: - owner: null - }; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/js/models/user.js b/frontend/js/models/user.js deleted file mode 100644 index a8e4ed9e9..000000000 --- a/frontend/js/models/user.js +++ /dev/null @@ -1,54 +0,0 @@ -const _ = require('underscore'); -const Backbone = require('backbone'); - -const model = Backbone.Model.extend({ - idAttribute: 'id', - - defaults: function () { - return { - id: undefined, - name: '', - nickname: '', - email: '', - is_disabled: false, - roles: [], - permissions: null - }; - }, - - /** - * @returns {Boolean} - */ - isAdmin: function () { - return _.indexOf(this.get('roles'), 'admin') !== -1; - }, - - /** - * Checks if the perm has either `view` or `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canView: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && ['view', 'manage'].indexOf(permissions[item]) !== -1; - }, - - /** - * Checks if the perm has `manage` value - * - * @param {String} item - * @returns {Boolean} - */ - canManage: function (item) { - let permissions = this.get('permissions'); - return permissions !== null && typeof permissions[item] !== 'undefined' && permissions[item] === 'manage'; - } -}); - -module.exports = { - Model: model, - Collection: Backbone.Collection.extend({ - model: model - }) -}; diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 60fa8bd9d..000000000 --- a/frontend/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "npmplus", - "version": "0.0.0", - "description": "A beautiful interface for creating Nginx endpoints", - "main": "js/index.js", - "dependencies": { - "@babel/core": "7.24.4", - "babel-core": "6.26.3", - "babel-loader": "8.3.0", - "babel-preset-env": "1.7.0", - "backbone": "1.6.0", - "backbone.marionette": "4.1.3", - "copy-webpack-plugin": "5.1.2", - "css-loader": "5.2.7", - "ejs-lint": "2.0.0", - "ejs-loader": "0.4.1", - "ejs-webpack-loader": "2.2.2", - "file-loader": "6.2.0", - "html-webpack-plugin": "4.5.2", - "imports-loader": "0.8.0", - "jquery": "3.7.1", - "jquery-mask-plugin": "1.14.16", - "jquery-serializejson": "3.2.1", - "marionette.approuter": "1.0.2", - "marionette.templatecache": "1.0.0", - "messageformat": "2.3.0", - "messageformat-loader": "0.8.1", - "mini-css-extract-plugin": "1.6.2", - "moment": "2.30.1", - "node-sass": "7.0.3", - "nodemon": "3.1.0", - "numeral": "2.0.6", - "sass-loader": "10.5.2", - "style-loader": "4.0.0", - "tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813", - "underscore": "1.13.6", - "webpack": "4.47.0", - "webpack-cli": "4.10.0", - "webpack-visualizer-plugin": "0.1.11" - }, - "scripts": { - "build": "webpack --mode production" - }, - "author": "Jamie Curnow and ZoeyVid ", - "license": "MIT" -} diff --git a/frontend/scss/custom.scss b/frontend/scss/custom.scss deleted file mode 100644 index 4037dcf6c..000000000 --- a/frontend/scss/custom.scss +++ /dev/null @@ -1,42 +0,0 @@ -$primary-color: #2bcbba; - -.loader { - color: $primary-color; -} - -a { - color: $primary-color; -} - -a:hover { - color: darken($primary-color, 10%); -} - -.dropdown-header { - padding-left: 1rem; -} - -.dropdown-item.active, .dropdown-item:active { - background-color: $primary-color; -} - -.custom-switch-input:checked ~ .custom-switch-indicator { - background: $primary-color; -} - -.min-100 { - min-height: 100px; -} - -.card-options .dropdown-menu a:not(.btn) { - margin-left: 0; -} - -.wrap { - display: flex; - flex-wrap: wrap; -} - -.col-login { - max-width: 48rem; -} \ No newline at end of file diff --git a/frontend/scss/fonts.scss b/frontend/scss/fonts.scss deleted file mode 100644 index f0ec1b739..000000000 --- a/frontend/scss/fonts.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* source-sans-pro-regular - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 400; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700italic - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: italic; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} - -/* source-sans-pro-700 - latin-ext_latin */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: local(''), - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ - url('../fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ -} diff --git a/frontend/scss/selectize.scss b/frontend/scss/selectize.scss deleted file mode 100644 index e12d5b694..000000000 --- a/frontend/scss/selectize.scss +++ /dev/null @@ -1,196 +0,0 @@ -.selectize-dropdown-header { - position: relative; - padding: 5px 8px; - background: #f8f8f8; - border-bottom: 1px solid #d0d0d0; - -webkit-border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - border-radius: 3px 3px 0 0; -} - -.selectize-dropdown-header-close { - position: absolute; - top: 50%; - right: 8px; - margin-top: -12px; - font-size: 20px !important; - line-height: 20px; - color: #303030; - opacity: 0.4; -} - -.selectize-dropdown-header-close:hover { - color: #000000; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup { - float: left; - border-top: 0 none; - border-right: 1px solid #f2f2f2; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child { - border-right: 0 none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup:before { - display: none; -} - -.selectize-dropdown.plugin-optgroup_columns .optgroup-header { - border-top: 0 none; -} - -.selectize-control.plugin-remove_button [data-value] { - position: relative; - padding-right: 24px !important; -} - -.selectize-control.plugin-remove_button [data-value] .remove { - position: absolute; - top: 0; - right: 0; - bottom: 0; - display: inline-block; - width: 17px; - padding: 2px 0 0 0; - font-size: 12px; - font-weight: bold; - color: inherit; - text-align: center; - text-decoration: none; - vertical-align: middle; - border-left: 1px solid #0073bb; - -webkit-border-radius: 0 2px 2px 0; - -moz-border-radius: 0 2px 2px 0; - border-radius: 0 2px 2px 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-control.plugin-remove_button [data-value] .remove:hover { - background: rgba(0, 0, 0, 0.05); -} - -.selectize-control.plugin-remove_button [data-value].active .remove { - border-left-color: #00578d; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover { - background: none; -} - -.selectize-control.plugin-remove_button .disabled [data-value] .remove { - border-left-color: #aaaaaa; -} - -.selectize-control { - position: relative; -} - -.selectize-dropdown { - font-family: inherit; - font-size: 13px; - -webkit-font-smoothing: inherit; - line-height: 18px; - color: #303030; -} - -.selectize-control.single { - display: inline-block; - cursor: text; - background: #ffffff; -} - -.selectize-dropdown { - position: absolute; - z-index: 10; - margin: -1px 0 0 0; - background: #ffffff; - border: 1px solid #d0d0d0; - border-top: 0 none; - -webkit-border-radius: 0 0 3px 3px; - -moz-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.selectize-dropdown [data-selectable] { - overflow: hidden; - cursor: pointer; -} - -.selectize-dropdown [data-selectable] .highlight { - background: rgba(125, 168, 208, 0.2); - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; -} - -.selectize-dropdown [data-selectable], -.selectize-dropdown .optgroup-header { - padding: 5px 8px; -} - -.selectize-dropdown .optgroup:first-child .optgroup-header { - border-top: 0 none; -} - -.selectize-dropdown .optgroup-header { - color: #303030; - cursor: default; - background: #ffffff; -} - -.selectize-dropdown .active { - color: #495c68; - background-color: #f5fafd; -} - -.selectize-dropdown .active.create { - color: #495c68; -} - -.selectize-dropdown .create { - color: rgba(48, 48, 48, 0.5); -} - -.selectize-dropdown-content { - max-height: 200px; - overflow-x: hidden; - overflow-y: auto; - - .title { - font-weight: bold; - } - - .description { - padding-left: 16px; - } -} - -.selectize-dropdown .optgroup-header { - padding-top: 7px; - font-size: 0.85em; - font-weight: bold; -} - -.selectize-dropdown .optgroup { - border-top: 1px solid #f0f0f0; -} - -.selectize-dropdown .optgroup:first-child { - border-top: 0 none; -} - -.custom-select { - height: auto; -} diff --git a/frontend/scss/styles.scss b/frontend/scss/styles.scss deleted file mode 100644 index 527330972..000000000 --- a/frontend/scss/styles.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "~tabler-ui/dist/assets/css/dashboard"; -@import "tabler-extra"; -@import "fonts"; -@import "selectize"; -@import "custom"; - -/* Before any JS content is loaded */ -#app > .loader, #login > .loader, .container > .loader { - position: absolute; - left: 49%; - top: 40%; - display: block; -} - -.no-js-warning { - margin-top: 100px; -} diff --git a/frontend/scss/tabler-extra.scss b/frontend/scss/tabler-extra.scss deleted file mode 100644 index fe757ba32..000000000 --- a/frontend/scss/tabler-extra.scss +++ /dev/null @@ -1,170 +0,0 @@ -$teal: #2bcbba; -$yellow: #f1c40f; -$blue: #467fcf; -$pink: #f66d9b; - -.tag { - margin-bottom: .5em; - margin-right: .5em; -} - -.tag.hover-green:hover, .tag.hover-green:active, .tag.hover-green:focus { - background-color: #5eba00; - cursor: pointer; - color: #fff; -} - -.tag.hover-red:hover, .tag.hover-red:active, .tag.hover-red:focus { - background-color: #cd201f; - cursor: pointer; - color: #fff; -} - -/* For Card bodies where I don't want padding */ -.card-body.no-padding { - padding: 0; -} - -/* For some reason this class doesn't have 'display: flex' when it should. https://preview.tabler.io/docs/buttons.html#list-of-buttons */ -.btn-list { - display: flex; -} - -/* Teal Outline Buttons */ -.btn-outline-teal { - color: $teal; - background-color: transparent; - background-image: none; - border-color: $teal; -} - -.btn-outline-teal:hover { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.btn-outline-teal:not(:disabled):not(.disabled):active, .btn-outline-teal:not(:disabled):not(.disabled).active, .show > .btn-outline-teal.dropdown-toggle { - color: #fff; - background-color: $teal; - border-color: $teal; -} - -.tag.hover-teal:hover, .tag.hover-teal:active, .tag.hover-teal:focus { - background-color: $teal; - color: #fff; - cursor: pointer; -} - -/* Yellow Outline Buttons */ -.btn-outline-yellow { - color: $yellow; - background-color: transparent; - background-image: none; - border-color: $yellow; -} - -.btn-outline-yellow:hover { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.btn-outline-yellow:not(:disabled):not(.disabled):active, .btn-outline-yellow:not(:disabled):not(.disabled).active, .show > .btn-outline-yellow.dropdown-toggle { - color: #fff; - background-color: $yellow; - border-color: $yellow; -} - -.tag.hover-yellow:hover, .tag.hover-yellow:active, .tag.hover-yellow:focus { - background-color: $yellow; - cursor: pointer; - color: #fff; -} - -/* Blue Outline Buttons */ -.btn-outline-blue { - color: $blue; - background-color: transparent; - background-image: none; - border-color: $blue; -} - -.btn-outline-blue:hover { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.btn-outline-blue:not(:disabled):not(.disabled):active, .btn-outline-blue:not(:disabled):not(.disabled).active, .show > .btn-outline-blue.dropdown-toggle { - color: #fff; - background-color: $blue; - border-color: $blue; -} - -.tag.hover-blue:hover, .tag.hover-blue:active, .tag.hover-blue:focus { - background-color: $blue; - cursor: pointer; - color: #fff; -} - -/* Pink Outline Buttons */ -.btn-outline-pink { - color: $pink; - background-color: transparent; - background-image: none; - border-color: $pink; -} - -.btn-outline-pink:hover { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.btn-outline-pink:not(:disabled):not(.disabled):active, .btn-outline-pink:not(:disabled):not(.disabled).active, .show > .btn-outline-pink.dropdown-toggle { - color: #fff; - background-color: $pink; - border-color: $pink; -} - -.tag.hover-pink:hover, .tag.hover-pink:active, .tag.hover-pink:focus { - background-color: $pink; - cursor: pointer; -} - -/* dimmer */ - -.dimmer .loader { - margin-top: 50px; -} - -/* modal tabs */ - -.modal-body.has-tabs { - padding: 0; - - .nav-tabs { - margin: 0; - } - - .tab-content { - padding: 1rem; - } -} - -/* modal wide */ - -@media (min-width: 576px) { - .modal-dialog.wide { - max-width: 700px; - margin: 1.75rem auto; - } -} - - -/* Form mod */ - -textarea.form-control.text-monospace { - font-size: 12px; -} diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js deleted file mode 100644 index 6ce1636a8..000000000 --- a/frontend/webpack.config.js +++ /dev/null @@ -1,143 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const Visualizer = require('webpack-visualizer-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const PACKAGE = require('./package.json'); - -module.exports = { - entry: { - main: './js/index.js', - login: './js/login.js' - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: `js/[name].bundle.js?v=${PACKAGE.version}`, - chunkFilename: `js/[name].bundle.[id].js?v=${PACKAGE.version}`, - publicPath: '/' - }, - resolve: { - alias: { - 'tabler-core': 'tabler-ui/dist/assets/js/core', - 'bootstrap': 'tabler-ui/dist/assets/js/vendors/bootstrap.bundle.min', - 'sparkline': 'tabler-ui/dist/assets/js/vendors/jquery.sparkline.min', - 'selectize': 'tabler-ui/dist/assets/js/vendors/selectize.min', - 'tablesorter': 'tabler-ui/dist/assets/js/vendors/jquery.tablesorter.min', - 'vector-map': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-2.0.3.min', - 'vector-map-de': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-de-merc', - 'vector-map-world': 'tabler-ui/dist/assets/js/vendors/jquery-jvectormap-world-mill', - 'circle-progress': 'tabler-ui/dist/assets/js/vendors/circle-progress.min', - 'c3': 'tabler-ui/dist/assets/js/vendors/chart.bundle.min' - } - }, - module: { - rules: [ - // Shims for tabler-ui - { - test: /assets\/js\/core/, - loader: 'imports-loader?bootstrap' - }, - { - test: /jquery-jvectormap-de-merc/, - loader: 'imports-loader?vector-map' - }, - { - test: /jquery-jvectormap-world-mill/, - loader: 'imports-loader?vector-map' - }, - - // other: - { - type: 'javascript/auto', // <= Set the module.type explicitly - test: /\bmessages\.json$/, - loader: 'messageformat-loader', - options: { - biDiSupport: false, - disablePluralKeyChecks: false, - formatters: null, - intlSupport: false, - locale: ['en'], - strictNumberSign: false - } - }, - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader' - } - }, - { - test: /\.ejs$/, - loader: 'ejs-loader' - }, - { - test: /\.scss$/, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader' - ] - }, - { - test: /.*tabler.*\.(jpe?g|gif|png|svg|eot|woff|ttf)$/, - use: [ - { - loader: 'file-loader', - options: { - outputPath: 'assets/tabler-ui/' - } - } - ] - }, - { - test: /source-sans-pro.*\.(woff(2)?)(\?v=\d+\.\d+\.\d+)?$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'assets/' - } - } - ] - } - ] - }, - plugins: [ - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - _: 'underscore' - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/index.ejs', - filename: 'index.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new HtmlWebpackPlugin({ - template: '!!ejs-webpack-loader!html/login.ejs', - filename: 'login.html', - inject: false, - templateParameters: { - version: PACKAGE.version - } - }), - new MiniCssExtractPlugin({ - filename: 'css/[name].css', - chunkFilename: 'css/[id].css' - }), - new Visualizer({ - filename: '../webpack_stats.html' - }), - new CopyWebpackPlugin([{ - from: 'app-images', - to: 'images', - toType: 'dir' - }]) - ] -}; diff --git a/global/README.md b/global/README.md deleted file mode 100644 index 0c7cac50b..000000000 --- a/global/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# certbot-dns-plugins - -This file contains info about available Certbot DNS plugins. -This only works for plugins which use the standard argument structure, so: ---authenticator ---credentials ---propagation-seconds - -File Structure: - -```json -{ - "cloudflare": { - "name": "Name displayed to the user", - "package_name": "Package name in PyPi repo", - "credentials": "Template of the credentials file", - "full_plugin_name": "The full plugin name as used in the commandline with certbot, e.g. 'dns-cloudflare'" - }, - ... -} -``` diff --git a/global/certbot-dns-plugins.json b/global/certbot-dns-plugins.json deleted file mode 100644 index 74b358de3..000000000 --- a/global/certbot-dns-plugins.json +++ /dev/null @@ -1,338 +0,0 @@ -{ - "acmedns": { - "name": "ACME-DNS", - "package_name": "certbot-dns-acmedns", - "credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/tls/certbot/acme-registration.json", - "full_plugin_name": "dns-acmedns" - }, - "aliyun": { - "name": "Aliyun", - "package_name": "certbot-dns-aliyun", - "credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef", - "full_plugin_name": "dns-aliyun" - }, - "azure": { - "name": "Azure", - "package_name": "certbot-dns-azure", - "credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2", - "full_plugin_name": "dns-azure" - }, - "bunny": { - "name": "bunny.net", - "package_name": "certbot-dns-bunny", - "credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", - "full_plugin_name": "dns-bunny" - }, - "cloudflare": { - "name": "Cloudflare", - "package_name": "certbot-dns-cloudflare", - "credentials": "# Cloudflare API token\ndns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567\n# OR Cloudflare API credentials\n#dns_cloudflare_email = cloudflare@example.com\n#dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234", - "full_plugin_name": "dns-cloudflare" - }, - "cloudns": { - "name": "ClouDNS", - "package_name": "certbot-dns-cloudns", - "credentials": "# Target user ID (see https://www.cloudns.net/api-settings/)\n\tdns_cloudns_auth_id=1234\n\t# Alternatively, one of the following two options can be set:\n\t# dns_cloudns_sub_auth_id=1234\n\t# dns_cloudns_sub_auth_user=foobar\n\n\t# API password\n\tdns_cloudns_auth_password=password1", - "full_plugin_name": "dns-cloudns" - }, - "cloudxns": { - "name": "CloudXNS", - "package_name": "certbot-dns-cloudxns", - "credentials": "dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef\ndns_cloudxns_secret_key = 1122334455667788", - "full_plugin_name": "dns-cloudxns" - }, - "constellix": { - "name": "Constellix", - "package_name": "certbot-dns-constellix", - "credentials": "dns_constellix_apikey = 5fb4e76f-ac91-43e5-f982458bc595\ndns_constellix_secretkey = 47d99fd0-32e7-4e07-85b46d08e70b\ndns_constellix_endpoint = https://api.dns.constellix.com/v1", - "full_plugin_name": "dns-constellix" - }, - "corenetworks": { - "name": "Core Networks", - "package_name": "certbot-dns-corenetworks", - "credentials": "dns_corenetworks_username = asaHB12r\ndns_corenetworks_password = secure_password", - "full_plugin_name": "dns-corenetworks" - }, - "cpanel": { - "name": "cPanel", - "package_name": "certbot-dns-cpanel", - "credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = user\ncpanel_password = hunter2", - "full_plugin_name": "cpanel" - }, - "desec": { - "name": "deSEC", - "package_name": "certbot-dns-desec", - "credentials": "dns_desec_token = YOUR_DESEC_API_TOKEN\ndns_desec_endpoint = https://desec.io/api/v1/", - "full_plugin_name": "dns-desec" - }, - "duckdns": { - "name": "DuckDNS", - "package_name": "certbot-dns-duckdns", - "credentials": "dns_duckdns_token=your-duckdns-token", - "full_plugin_name": "dns-duckdns" - }, - "digitalocean": { - "name": "DigitalOcean", - "package_name": "certbot-dns-digitalocean", - "credentials": "dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff", - "full_plugin_name": "dns-digitalocean" - }, - "directadmin": { - "name": "DirectAdmin", - "package_name": "certbot-dns-directadmin", - "credentials": "directadmin_url = https://my.directadminserver.com:2222\ndirectadmin_username = username\ndirectadmin_password = aSuperStrongPassword", - "full_plugin_name": "directadmin" - }, - "dnsimple": { - "name": "DNSimple", - "package_name": "certbot-dns-dnsimple", - "credentials": "dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw", - "full_plugin_name": "dns-dnsimple" - }, - "dnsmadeeasy": { - "name": "DNS Made Easy", - "package_name": "certbot-dns-dnsmadeeasy", - "credentials": "dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a\ndns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55", - "full_plugin_name": "dns-dnsmadeeasy" - }, - "dnspod": { - "name": "DNSPod", - "package_name": "certbot-dnspod", - "credentials": "certbot_dnspod_token = \ncertbot_dnspod_token_id = ", - "full_plugin_name": "certbot-dnspod" - }, - "domainoffensive": { - "name": "DomainOffensive (do.de)", - "package_name": "certbot-dns-do", - "credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN", - "full_plugin_name": "dns-do" - }, - "domeneshop": { - "name": "Domeneshop", - "package_name": "certbot-dns-domeneshop", - "credentials": "dns_domeneshop_client_token=YOUR_DOMENESHOP_CLIENT_TOKEN\ndns_domeneshop_client_secret=YOUR_DOMENESHOP_CLIENT_SECRET", - "full_plugin_name": "dns-domeneshop" - }, - "dynu": { - "name": "Dynu", - "package_name": "certbot-dns-dynu", - "credentials": "dns_dynu_auth_token = YOUR_DYNU_AUTH_TOKEN", - "full_plugin_name": "dns-dynu" - }, - "easydns": { - "name": "easyDNS", - "package_name": "certbot-dns-easydns", - "credentials": "dns_easydns_usertoken = YOUR_EASYDNS_USERTOKEN\ndns_easydns_userkey = YOUR_EASYDNS_USERKEY\ndns_easydns_endpoint = https://rest.easydns.net", - "full_plugin_name": "dns-easydns" - }, - "eurodns": { - "name": "EuroDNS", - "package_name": "certbot-dns-eurodns", - "credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", - "full_plugin_name": "dns-eurodns" - }, - "freedns": { - "name": "FreeDNS", - "package_name": "certbot-dns-freedns", - "credentials": "dns_freedns_username = myremoteuser\ndns_freedns_password = verysecureremoteuserpassword", - "full_plugin_name": "dns-freedns" - }, - "gandi": { - "name": "Gandi Live DNS", - "package_name": "certbot_plugin_gandi", - "credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", - "full_plugin_name": "dns-gandi" - }, - "godaddy": { - "name": "GoDaddy", - "package_name": "certbot-dns-godaddy", - "credentials": "dns_godaddy_secret = 0123456789abcdef0123456789abcdef01234567\ndns_godaddy_key = abcdef0123456789abcdef01234567abcdef0123", - "full_plugin_name": "dns-godaddy" - }, - "google": { - "name": "Google", - "package_name": "certbot-dns-google", - "credentials": "{\n\"type\": \"service_account\",\n...\n}", - "full_plugin_name": "dns-google" - }, - "googledomains": { - "name": "GoogleDomainsDNS", - "package_name": "certbot-dns-google-domains", - "credentials": "dns_google_domains_access_token = 0123456789abcdef0123456789abcdef01234567\ndns_google_domains_zone = \"example.com\"", - "full_plugin_name": "dns-google-domains" - }, - "he": { - "name": "Hurricane Electric", - "package_name": "certbot-dns-he", - "credentials": "dns_he_user = Me\ndns_he_pass = my HE password", - "full_plugin_name": "dns-he" - }, - "hetzner": { - "name": "Hetzner", - "package_name": "certbot-dns-hetzner", - "credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef", - "full_plugin_name": "dns-hetzner" - }, - "infomaniak": { - "name": "Infomaniak", - "package_name": "certbot-dns-infomaniak", - "credentials": "dns_infomaniak_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - "full_plugin_name": "dns-infomaniak" - }, - "inwx": { - "name": "INWX", - "package_name": "certbot-dns-inwx", - "credentials": "dns_inwx_url = https://api.domrobot.com/xmlrpc/\ndns_inwx_username = your_username\ndns_inwx_password = your_password\ndns_inwx_shared_secret = your_shared_secret optional", - "full_plugin_name": "dns-inwx" - }, - "ionos": { - "name": "IONOS", - "package_name": "certbot-dns-ionos", - "credentials": "dns_ionos_prefix = myapikeyprefix\ndns_ionos_secret = verysecureapikeysecret\ndns_ionos_endpoint = https://api.hosting.ionos.com", - "full_plugin_name": "dns-ionos" - }, - "ispconfig": { - "name": "ISPConfig", - "package_name": "certbot-dns-ispconfig", - "credentials": "dns_ispconfig_username = myremoteuser\ndns_ispconfig_password = verysecureremoteuserpassword\ndns_ispconfig_endpoint = https://localhost:8080", - "full_plugin_name": "dns-ispconfig" - }, - "isset": { - "name": "Isset", - "package_name": "certbot-dns-isset", - "credentials": "dns_isset_endpoint=\"https://customer.isset.net/api\"\ndns_isset_token=\"\"", - "full_plugin_name": "dns-isset" - }, - "joker": { - "name": "Joker", - "package_name": "certbot-dns-joker", - "credentials": "dns_joker_username = \ndns_joker_password = \ndns_joker_domain = ", - "full_plugin_name": "dns-joker" - }, - "linode": { - "name": "Linode", - "package_name": "certbot-dns-linode", - "credentials": "dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64\ndns_linode_version = [|3|4]", - "full_plugin_name": "dns-linode" - }, - "loopia": { - "name": "Loopia", - "package_name": "certbot-dns-loopia", - "credentials": "dns_loopia_user = user@loopiaapi\ndns_loopia_password = abcdef0123456789abcdef01234567abcdef0123", - "full_plugin_name": "dns-loopia" - }, - "luadns": { - "name": "LuaDNS", - "package_name": "certbot-dns-luadns", - "credentials": "dns_luadns_email = user@example.com\ndns_luadns_token = 0123456789abcdef0123456789abcdef", - "full_plugin_name": "dns-luadns" - }, - "namecheap": { - "name": "Namecheap", - "package_name": "certbot-dns-namecheap", - "credentials": "dns_namecheap_username = 123456\ndns_namecheap_api_key = 0123456789abcdef0123456789abcdef01234567", - "full_plugin_name": "dns-namecheap" - }, - "netcup": { - "name": "netcup", - "package_name": "certbot-dns-netcup", - "credentials": "dns_netcup_customer_id = 123456\ndns_netcup_api_key = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", - "full_plugin_name": "dns-netcup" - }, - "njalla": { - "name": "Njalla", - "package_name": "certbot-dns-njalla", - "credentials": "dns_njalla_token = 0123456789abcdef0123456789abcdef01234567", - "full_plugin_name": "dns-njalla" - }, - "nsone": { - "name": "NS1", - "package_name": "certbot-dns-nsone", - "credentials": "dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw", - "full_plugin_name": "dns-nsone" - }, - "oci": { - "name": "Oracle Cloud Infrastructure DNS", - "package_name": "certbot-dns-oci", - "credentials": "[DEFAULT]\nuser = ocid1.user.oc1...\nfingerprint = xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx\ntenancy = ocid1.tenancy.oc1...\nregion = us-ashburn-1\nkey_file = ~/.oci/oci_api_key.pem", - "full_plugin_name": "dns-oci" - }, - "online": { - "name": "Online", - "package_name": "certbot-dns-online", - "credentials": "dns_online_token=0123456789abcdef0123456789abcdef01234567", - "full_plugin_name": "dns-online" - }, - "ovh": { - "name": "OVH", - "package_name": "certbot-dns-ovh", - "credentials": "dns_ovh_endpoint = ovh-eu\ndns_ovh_application_key = MDAwMDAwMDAwMDAw\ndns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw\ndns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw", - "full_plugin_name": "dns-ovh" - }, - "plesk": { - "name": "Plesk", - "package_name": "certbot-dns-plesk", - "credentials": "dns_plesk_username = your-username\ndns_plesk_password = secret\ndns_plesk_api_url = https://plesk-api-host:8443", - "full_plugin_name": "dns-plesk" - }, - "porkbun": { - "name": "Porkbun", - "package_name": "certbot-dns-porkbun", - "credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret", - "full_plugin_name": "dns-porkbun" - }, - "powerdns": { - "name": "PowerDNS", - "package_name": "certbot-dns-powerdns", - "credentials": "dns_powerdns_api_url = https://api.mypowerdns.example.org\ndns_powerdns_api_key = AbCbASsd!@34", - "full_plugin_name": "dns-powerdns" - }, - "regru": { - "name": "reg.ru", - "package_name": "certbot-regru", - "credentials": "dns_username=username\ndns_password=password", - "full_plugin_name": "dns" - }, - "rfc2136": { - "name": "RFC 2136", - "package_name": "certbot-dns-rfc2136", - "credentials": "# Target DNS server\ndns_rfc2136_server = 192.0.2.1\n# Target DNS port\ndns_rfc2136_port = 53\n# TSIG key name\ndns_rfc2136_name = keyname.\n# TSIG key secret\ndns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs AmKd7ak51vWKgSl12ib86oQRPkpDjg==\n# TSIG key algorithm\ndns_rfc2136_algorithm = HMAC-SHA512", - "full_plugin_name": "dns-rfc2136" - }, - "strato": { - "name": "Strato", - "package_name": "certbot-dns-strato", - "credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if you are using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if you are not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", - "full_plugin_name": "dns-strato" - }, - "timeweb": { - "name": "Timeweb Cloud", - "package_name": "certbot-dns-timeweb", - "credentials": "dns_timeweb_api_key = XXXXXXXXXXXXXXXXXXX", - "full_plugin_name": "dns-timeweb" - }, - "transip": { - "name": "TransIP", - "package_name": "certbot-dns-transip", - "credentials": "dns_transip_username = my_username\ndns_transip_key_file = /data/tls/certbot/transip-rsa.key", - "full_plugin_name": "dns-transip" - }, - "tencentcloud": { - "name": "Tencent Cloud", - "package_name": "certbot-dns-tencentcloud", - "credentials": "dns_tencentcloud_secret_id = TENCENT_CLOUD_SECRET_ID\ndns_tencentcloud_secret_key = TENCENT_CLOUD_SECRET_KEY", - "full_plugin_name": "dns-tencentcloud" - }, - "vultr": { - "name": "Vultr", - "package_name": "certbot-dns-vultr", - "credentials": "dns_vultr_key = YOUR_VULTR_API_KEY", - "full_plugin_name": "dns-vultr" - }, - "websupport": { - "name": "Websupport.sk", - "package_name": "certbot-dns-websupport", - "credentials": "dns_websupport_identifier = \ndns_websupport_secret_key = ", - "full_plugin_name": "dns-websupport" - } -} From 7a6a9b8d524872581955cd314ef18ceb1dd7abb0 Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 20 Apr 2024 18:38:03 +0200 Subject: [PATCH 005/137] init PHP rewrite Signed-off-by: Zoey --- .github/workflows/docker.yml | 10 +- .github/workflows/spellcheck.yml | 2 +- .github/workflows/update-and-lint.yml | 17 +- COPYING | 661 +++ Dockerfile | 58 +- compose.override.yaml | 14 - compose.yaml | 15 +- renovate.json | 2 +- rootfs/etc/{tls => }/dhparam | 0 rootfs/etc/tls/certbot.ini | 15 - rootfs/html/404/bootstrap.min.css | 6 - rootfs/html/404/index.html | 26 - rootfs/html/default/bootstrap.min.css | 6 - rootfs/html/default/index.html | 29 - rootfs/usr/local/bin/healthcheck.sh | 2 +- rootfs/usr/local/bin/launch.sh | 13 +- rootfs/usr/local/bin/start.sh | 98 +- .../conf/conf.d/include/tls-ciphers.conf | 2 +- rootfs/usr/local/nginx/conf/conf.d/npm.conf | 36 - ...-name.conf => npmplus-no-server-name.conf} | 0 .../usr/local/nginx/conf/conf.d/npmplus.conf | 27 + src/composer.json | 37 + src/composer.lock | 259 + .../public/.well-known/security.txt | 0 src/public/status/index.php | 2 + src/vendor/autoload.php | 25 + .../chillerlan/php-qrcode/LICENSE-ASL-2.0 | 202 + src/vendor/chillerlan/php-qrcode/LICENSE-MIT | 21 + src/vendor/chillerlan/php-qrcode/NOTICE | 40 + src/vendor/chillerlan/php-qrcode/README.md | 168 + .../chillerlan/php-qrcode/composer.json | 79 + .../php-qrcode/src/Common/BitBuffer.php | 180 + .../php-qrcode/src/Common/ECICharset.php | 125 + .../php-qrcode/src/Common/EccLevel.php | 223 + .../src/Common/GDLuminanceSource.php | 97 + .../php-qrcode/src/Common/GF256.php | 154 + .../php-qrcode/src/Common/GenericGFPoly.php | 263 + .../src/Common/IMagickLuminanceSource.php | 78 + .../src/Common/LuminanceSourceAbstract.php | 104 + .../src/Common/LuminanceSourceInterface.php | 61 + .../php-qrcode/src/Common/MaskPattern.php | 329 ++ .../chillerlan/php-qrcode/src/Common/Mode.php | 96 + .../php-qrcode/src/Common/Version.php | 287 + .../php-qrcode/src/Data/AlphaNum.php | 137 + .../chillerlan/php-qrcode/src/Data/Byte.php | 85 + .../chillerlan/php-qrcode/src/Data/ECI.php | 155 + .../chillerlan/php-qrcode/src/Data/Hanzi.php | 205 + .../chillerlan/php-qrcode/src/Data/Kanji.php | 191 + .../chillerlan/php-qrcode/src/Data/Number.php | 182 + .../src/Data/QRCodeDataException.php | 20 + .../chillerlan/php-qrcode/src/Data/QRData.php | 263 + .../src/Data/QRDataModeAbstract.php | 61 + .../src/Data/QRDataModeInterface.php | 63 + .../php-qrcode/src/Data/QRMatrix.php | 812 +++ .../src/Data/ReedSolomonEncoder.php | 127 + .../php-qrcode/src/Decoder/Binarizer.php | 361 ++ .../php-qrcode/src/Decoder/BitMatrix.php | 430 ++ .../php-qrcode/src/Decoder/Decoder.php | 173 + .../php-qrcode/src/Decoder/DecoderResult.php | 99 + .../src/Decoder/QRCodeDecoderException.php | 20 + .../src/Decoder/ReedSolomonDecoder.php | 313 + .../src/Detector/AlignmentPattern.php | 34 + .../src/Detector/AlignmentPatternFinder.php | 283 + .../php-qrcode/src/Detector/Detector.php | 350 ++ .../php-qrcode/src/Detector/FinderPattern.php | 92 + .../src/Detector/FinderPatternFinder.php | 770 +++ .../php-qrcode/src/Detector/GridSampler.php | 181 + .../src/Detector/PerspectiveTransform.php | 182 + .../src/Detector/QRCodeDetectorException.php | 20 + .../php-qrcode/src/Detector/ResultPoint.php | 73 + .../src/Output/QRCodeOutputException.php | 20 + .../php-qrcode/src/Output/QREps.php | 173 + .../php-qrcode/src/Output/QRFpdf.php | 177 + .../php-qrcode/src/Output/QRGdImage.php | 400 ++ .../php-qrcode/src/Output/QRGdImageBMP.php | 33 + .../php-qrcode/src/Output/QRGdImageGIF.php | 33 + .../php-qrcode/src/Output/QRGdImageJPEG.php | 40 + .../php-qrcode/src/Output/QRGdImagePNG.php | 33 + .../php-qrcode/src/Output/QRGdImageWEBP.php | 33 + .../php-qrcode/src/Output/QRImage.php | 19 + .../php-qrcode/src/Output/QRImagick.php | 235 + .../php-qrcode/src/Output/QRMarkup.php | 94 + .../php-qrcode/src/Output/QRMarkupHTML.php | 51 + .../php-qrcode/src/Output/QRMarkupSVG.php | 200 + .../src/Output/QROutputAbstract.php | 261 + .../src/Output/QROutputInterface.php | 226 + .../php-qrcode/src/Output/QRString.php | 111 + .../php-qrcode/src/Output/QRStringJSON.php | 67 + .../php-qrcode/src/Output/QRStringText.php | 76 + .../chillerlan/php-qrcode/src/QRCode.php | 488 ++ .../php-qrcode/src/QRCodeException.php | 20 + .../chillerlan/php-qrcode/src/QROptions.php | 20 + .../php-qrcode/src/QROptionsTrait.php | 729 +++ .../chillerlan/php-settings-container/LICENSE | 21 + .../php-settings-container/README.md | 164 + .../php-settings-container/composer.json | 51 + .../src/SettingsContainerAbstract.php | 240 + .../src/SettingsContainerInterface.php | 78 + src/vendor/composer/ClassLoader.php | 579 ++ src/vendor/composer/InstalledVersions.php | 359 ++ LICENSE => src/vendor/composer/LICENSE | 12 +- src/vendor/composer/autoload_classmap.php | 10 + src/vendor/composer/autoload_namespaces.php | 9 + src/vendor/composer/autoload_psr4.php | 12 + src/vendor/composer/autoload_real.php | 38 + src/vendor/composer/autoload_static.php | 49 + src/vendor/composer/installed.json | 255 + src/vendor/composer/installed.php | 50 + src/vendor/composer/platform_check.php | 26 + src/vendor/phpmailer/phpmailer/.editorconfig | 15 + src/vendor/phpmailer/phpmailer/COMMITMENT | 46 + src/vendor/phpmailer/phpmailer/LICENSE | 502 ++ src/vendor/phpmailer/phpmailer/README.md | 231 + src/vendor/phpmailer/phpmailer/SECURITY.md | 37 + src/vendor/phpmailer/phpmailer/VERSION | 1 + src/vendor/phpmailer/phpmailer/composer.json | 79 + .../phpmailer/phpmailer/get_oauth_token.php | 182 + .../phpmailer/language/phpmailer.lang-af.php | 26 + .../phpmailer/language/phpmailer.lang-ar.php | 27 + .../phpmailer/language/phpmailer.lang-as.php | 35 + .../phpmailer/language/phpmailer.lang-az.php | 27 + .../phpmailer/language/phpmailer.lang-ba.php | 27 + .../phpmailer/language/phpmailer.lang-be.php | 27 + .../phpmailer/language/phpmailer.lang-bg.php | 27 + .../phpmailer/language/phpmailer.lang-bn.php | 35 + .../phpmailer/language/phpmailer.lang-ca.php | 27 + .../phpmailer/language/phpmailer.lang-cs.php | 28 + .../phpmailer/language/phpmailer.lang-da.php | 36 + .../phpmailer/language/phpmailer.lang-de.php | 28 + .../phpmailer/language/phpmailer.lang-el.php | 33 + .../phpmailer/language/phpmailer.lang-eo.php | 26 + .../phpmailer/language/phpmailer.lang-es.php | 31 + .../phpmailer/language/phpmailer.lang-et.php | 28 + .../phpmailer/language/phpmailer.lang-fa.php | 28 + .../phpmailer/language/phpmailer.lang-fi.php | 27 + .../phpmailer/language/phpmailer.lang-fo.php | 27 + .../phpmailer/language/phpmailer.lang-fr.php | 37 + .../phpmailer/language/phpmailer.lang-gl.php | 27 + .../phpmailer/language/phpmailer.lang-he.php | 27 + .../phpmailer/language/phpmailer.lang-hi.php | 35 + .../phpmailer/language/phpmailer.lang-hr.php | 27 + .../phpmailer/language/phpmailer.lang-hu.php | 27 + .../phpmailer/language/phpmailer.lang-hy.php | 27 + .../phpmailer/language/phpmailer.lang-id.php | 31 + .../phpmailer/language/phpmailer.lang-it.php | 28 + .../phpmailer/language/phpmailer.lang-ja.php | 29 + .../phpmailer/language/phpmailer.lang-ka.php | 27 + .../phpmailer/language/phpmailer.lang-ko.php | 27 + .../phpmailer/language/phpmailer.lang-lt.php | 27 + .../phpmailer/language/phpmailer.lang-lv.php | 27 + .../phpmailer/language/phpmailer.lang-mg.php | 27 + .../phpmailer/language/phpmailer.lang-mn.php | 27 + .../phpmailer/language/phpmailer.lang-ms.php | 27 + .../phpmailer/language/phpmailer.lang-nb.php | 33 + .../phpmailer/language/phpmailer.lang-nl.php | 34 + .../phpmailer/language/phpmailer.lang-pl.php | 33 + .../phpmailer/language/phpmailer.lang-pt.php | 27 + .../language/phpmailer.lang-pt_br.php | 38 + .../phpmailer/language/phpmailer.lang-ro.php | 33 + .../phpmailer/language/phpmailer.lang-ru.php | 28 + .../phpmailer/language/phpmailer.lang-si.php | 34 + .../phpmailer/language/phpmailer.lang-sk.php | 30 + .../phpmailer/language/phpmailer.lang-sl.php | 36 + .../phpmailer/language/phpmailer.lang-sr.php | 28 + .../language/phpmailer.lang-sr_latn.php | 28 + .../phpmailer/language/phpmailer.lang-sv.php | 27 + .../phpmailer/language/phpmailer.lang-tl.php | 28 + .../phpmailer/language/phpmailer.lang-tr.php | 31 + .../phpmailer/language/phpmailer.lang-uk.php | 28 + .../phpmailer/language/phpmailer.lang-vi.php | 27 + .../phpmailer/language/phpmailer.lang-zh.php | 29 + .../language/phpmailer.lang-zh_cn.php | 36 + .../phpmailer/src/DSNConfigurator.php | 245 + .../phpmailer/phpmailer/src/Exception.php | 40 + src/vendor/phpmailer/phpmailer/src/OAuth.php | 139 + .../phpmailer/src/OAuthTokenProvider.php | 44 + .../phpmailer/phpmailer/src/PHPMailer.php | 5252 +++++++++++++++++ src/vendor/phpmailer/phpmailer/src/POP3.php | 467 ++ src/vendor/phpmailer/phpmailer/src/SMTP.php | 1497 +++++ 179 files changed, 25155 insertions(+), 287 deletions(-) create mode 100644 COPYING delete mode 100644 compose.override.yaml rename rootfs/etc/{tls => }/dhparam (100%) delete mode 100644 rootfs/etc/tls/certbot.ini delete mode 100644 rootfs/html/404/bootstrap.min.css delete mode 100644 rootfs/html/404/index.html delete mode 100644 rootfs/html/default/bootstrap.min.css delete mode 100644 rootfs/html/default/index.html delete mode 100644 rootfs/usr/local/nginx/conf/conf.d/npm.conf rename rootfs/usr/local/nginx/conf/conf.d/{npm-no-server-name.conf => npmplus-no-server-name.conf} (100%) create mode 100644 rootfs/usr/local/nginx/conf/conf.d/npmplus.conf create mode 100644 src/composer.json create mode 100644 src/composer.lock rename security.txt => src/public/.well-known/security.txt (100%) create mode 100644 src/public/status/index.php create mode 100644 src/vendor/autoload.php create mode 100644 src/vendor/chillerlan/php-qrcode/LICENSE-ASL-2.0 create mode 100644 src/vendor/chillerlan/php-qrcode/LICENSE-MIT create mode 100644 src/vendor/chillerlan/php-qrcode/NOTICE create mode 100644 src/vendor/chillerlan/php-qrcode/README.md create mode 100644 src/vendor/chillerlan/php-qrcode/composer.json create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GF256.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/Mode.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Common/Version.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Byte.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/ECI.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/Number.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRData.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QREps.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRString.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QRCode.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QRCodeException.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QROptions.php create mode 100644 src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php create mode 100644 src/vendor/chillerlan/php-settings-container/LICENSE create mode 100644 src/vendor/chillerlan/php-settings-container/README.md create mode 100644 src/vendor/chillerlan/php-settings-container/composer.json create mode 100644 src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php create mode 100644 src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php create mode 100644 src/vendor/composer/ClassLoader.php create mode 100644 src/vendor/composer/InstalledVersions.php rename LICENSE => src/vendor/composer/LICENSE (87%) create mode 100644 src/vendor/composer/autoload_classmap.php create mode 100644 src/vendor/composer/autoload_namespaces.php create mode 100644 src/vendor/composer/autoload_psr4.php create mode 100644 src/vendor/composer/autoload_real.php create mode 100644 src/vendor/composer/autoload_static.php create mode 100644 src/vendor/composer/installed.json create mode 100644 src/vendor/composer/installed.php create mode 100644 src/vendor/composer/platform_check.php create mode 100644 src/vendor/phpmailer/phpmailer/.editorconfig create mode 100644 src/vendor/phpmailer/phpmailer/COMMITMENT create mode 100644 src/vendor/phpmailer/phpmailer/LICENSE create mode 100644 src/vendor/phpmailer/phpmailer/README.md create mode 100644 src/vendor/phpmailer/phpmailer/SECURITY.md create mode 100644 src/vendor/phpmailer/phpmailer/VERSION create mode 100644 src/vendor/phpmailer/phpmailer/composer.json create mode 100644 src/vendor/phpmailer/phpmailer/get_oauth_token.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php create mode 100644 src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php create mode 100644 src/vendor/phpmailer/phpmailer/src/Exception.php create mode 100644 src/vendor/phpmailer/phpmailer/src/OAuth.php create mode 100644 src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php create mode 100644 src/vendor/phpmailer/phpmailer/src/PHPMailer.php create mode 100644 src/vendor/phpmailer/phpmailer/src/POP3.php create mode 100644 src/vendor/phpmailer/phpmailer/src/SMTP.php diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e03d40ef0..a7cc3e93c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,18 +6,12 @@ on: paths: - .github/workflows/docker.yml - Dockerfile - - frontend/** - - backend/** - - global/** - rootfs/** - src/** pull_request: paths: - .github/workflows/docker.yml - Dockerfile - - frontend/** - - backend/** - - global/** - rootfs/** - src/** workflow_dispatch: @@ -56,9 +50,7 @@ jobs: - name: version run: | version="$(cat .version)+$(git rev-parse --short HEAD)" - sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/js/i18n/messages.json - sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/package.json - sed -i "s|\"0.0.0\"|\"$version\"|g" backend/package.json + #sed -i "s|\"0.0.0\"|\"$version\"|g" src/ - name: Build uses: docker/build-push-action@v5 if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 381c8a157..f746ced85 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -15,4 +15,4 @@ jobs: with: check_filenames: true check_hidden: true - skip: .git,.gitignore,showdown.min.js,jquery.min.js,xregexp-all.js + skip: .git,.gitignore,./rootfs/nftd,./src/vendor diff --git a/.github/workflows/update-and-lint.yml b/.github/workflows/update-and-lint.yml index 7de0d0f81..e4b21f601 100644 --- a/.github/workflows/update-and-lint.yml +++ b/.github/workflows/update-and-lint.yml @@ -3,6 +3,7 @@ on: push: branches: - develop + - php schedule: - cron: "0 */6 * * *" workflow_dispatch: @@ -13,21 +14,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 21 - - name: eslint + - name: lint run: | - cd backend - yarn install --no-lockfile - yarn eslint . --fix - - name: update - run: | - curl -L https://unpkg.com/xregexp/xregexp-all.js -o rootfs/nftd/xregexp-all.js - curl -L https://unpkg.com/showdown/dist/showdown.min.js -o rootfs/nftd/showdown.min.js - curl -L https://code.jquery.com/jquery-"$(git ls-remote --tags https://github.com/jquery/jquery | cut -d/ -f3 | sort -V | tail -1)".min.js -o rootfs/nftd/jquery.min.js - curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/404/bootstrap.min.css - curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/default/bootstrap.min.css + cd src - name: nginxbeautifier run: | yarn global add nginxbeautifier diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..0ad25db4b --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Dockerfile b/Dockerfile index bc4e23a46..38f3e1d4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,3 @@ -# syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as frontend -COPY frontend /build/frontend -COPY global/certbot-dns-plugins.json /build/frontend/certbot-dns-plugins.json -ARG NODE_ENV=production \ - NODE_OPTIONS=--openssl-legacy-provider -WORKDIR /build/frontend -RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs yarn git python3 build-base && \ - yarn global add clean-modules && \ - yarn --no-lockfile install && \ - clean-modules --yes && \ - yarn --no-lockfile build && \ - yarn cache clean --all -COPY darkmode.css /build/frontend/dist/css/darkmode.css -COPY security.txt /build/frontend/dist/.well-known/security.txt - - -FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as backend -SHELL ["/bin/ash", "-eo", "pipefail", "-c"] -COPY backend /build/backend -COPY global/certbot-dns-plugins.json /build/backend/certbot-dns-plugins.json -ARG NODE_ENV=production \ - TARGETARCH -WORKDIR /build/backend -RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs-current yarn && \ - yarn global add clean-modules && \ - if [ "$TARGETARCH" = "amd64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=x64 yarn install --no-lockfile; \ - elif [ "$TARGETARCH" = "arm64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=arm64 yarn install --no-lockfile; \ - fi && \ - clean-modules --yes && \ - yarn cache clean --all - - FROM --platform="$BUILDPLATFORM" alpine:3.19.1 as crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] @@ -64,18 +27,19 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CRS_VER=v4.1.0 COPY rootfs / -COPY --from=zoeyvid/certbot-docker:34 /usr/local /usr/local +COPY src /html/app COPY --from=zoeyvid/curl-quic:380 /usr/local/bin/curl /usr/local/bin/curl RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ bash nano \ - nodejs-current \ openssl apache2-utils \ lua5.1-lzlib lua5.1-socket \ coreutils grep findutils jq shadow su-exec \ - luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn && \ + luarocks5.1 lua5.1-dev lua5.1-sec build-base git \ + fcgi php83-fpm php83-phar php83-iconv php83-mbstring php83-openssl php83-ctype php83-curl php83-session php-sqlite3 && \ curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --home /usr/local/acme.sh --nocron && \ + ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \ git clone https://github.com/coreruleset/coreruleset --branch "$CRS_VER" /tmp/coreruleset && \ mkdir -v /usr/local/nginx/conf/conf.d/include/coreruleset && \ mv -v /tmp/coreruleset/crs-setup.conf.example /usr/local/nginx/conf/conf.d/include/coreruleset/crs-setup.conf.example && \ @@ -86,11 +50,8 @@ RUN apk upgrade --no-cache -a && \ luarocks-5.1 install lua-resty-http && \ luarocks-5.1 install lua-resty-string && \ luarocks-5.1 install lua-resty-openssl && \ - yarn global add nginxbeautifier && \ - apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn + apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git -COPY --from=backend /build/backend /app -COPY --from=frontend /build/frontend/dist /html/frontend COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html @@ -98,18 +59,12 @@ COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf -RUN ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \ - ln -s /app/password-reset.js /usr/local/bin/password-reset.js && \ - ln -s /app/sqlite-vaccum.js /usr/local/bin/sqlite-vaccum.js && \ - ln -s /app/index.js /usr/local/bin/index.js - ENV NODE_ENV=production \ NODE_CONFIG_DIR=/data/etc/npm \ DB_SQLITE_FILE=/data/etc/npm/database.sqlite ENV PUID=0 \ PGID=0 \ - NIBEP=48693 \ GOAIWSP=48683 \ NPM_PORT=81 \ GOA_PORT=91 \ @@ -141,8 +96,7 @@ ENV PUID=0 \ GOA=false \ GOACLA="--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=1 --keep-last=30 --with-output-resolver --no-query-string" \ PHP81=false \ - PHP82=false \ - PHP83=false + PHP82=false WORKDIR /app ENTRYPOINT ["tini", "--", "entrypoint.sh"] diff --git a/compose.override.yaml b/compose.override.yaml deleted file mode 100644 index 709aaea14..000000000 --- a/compose.override.yaml +++ /dev/null @@ -1,14 +0,0 @@ -services: - npmplus-caddy: - container_name: npmplus-caddy - image: zoeyvid/npmplus:caddy - restart: always - network_mode: bridge - ports: - - "80:80" - environment: - - "TZ=Europe/Berlin" - - npmplus: - environment: - - "DISABLE_HTTP=true" # disables nginx to listen on port 80, default false diff --git a/compose.yaml b/compose.yaml index cb8b32edb..b2bd31dbb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -12,7 +12,6 @@ services: - "TZ=Europe/Berlin" # set timezone, required # - "PUID=1000" # set group id, default 0 (root) # - "PGID=1000" # set user id, default 0 (root) -# - "NIBEP=48694" # internal port of the NOMplus API, always bound to 127.0.0.1, default 48693, you need to change it, if you want to run multiple npm instances in network mode host # - "GOAIWSP=48684" # internal port of goaccess, always bound to 127.0.0.1, default 48683, you need to change it, if you want to run multiple npm with goaccess instances in network mode host # - "NPM_PORT=82" # Port the NPM UI should be bound to, default 81, you need to change it, if you want to run multiple npm instances in network mode host # - "NPM_PORT=92" # Port the goaccess should be bound to, default 91, you need to change it, if you want to run multiple npm with goaccess instances in network mode host @@ -42,10 +41,20 @@ services: # - "IPRT=3" # Set how many hours should be between updating ip ranges from aws and cloudflare, default 1, ignored when SKIP_IP_RANGES is true # - "GOA=true" # Enables goaccess, overrides LOGROTATE, default false --- if you download the GeoLite2-Country.mmdb, GeoLite2-City.mmdb AND GeoLite2-ASN.mmdb file from MaxMind and place them in /opt/npm/etc/goaccess/geoip it will automatically enable GeoIP in goaccess after restarting NPMplus (no need to change GOACLA below), you may also use the compose.geoip.yaml # - "GOACLA=--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=2 --keep-last=7 --with-output-resolver --no-query-string" # Arguments that should be passed to goaccess, default: https://github.com/ZoeyVid/NPMplus/blob/develop/rootfs/usr/local/bin/launch.sh#L50 and: --agent-list --real-os --double-decode --anonymize-ip --anonymize-level=1 --keep-last=30 --with-output-resolver --no-query-string +# - "PHP_APKS=php-pecl-apcu php-pecl-redis" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php-*, default none # - "PHP81=true" # Activate PHP81, default false # - "PHP81_APKS=php81-curl php81-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php81-*, default none # - "PHP82=true" # Activate PHP82, default false # - "PHP82_APKS=php82-curl php-82-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php82-*, default none -# - "PHP83=true" # Activate PHP83, default false # - "PHP83_APKS=php83-curl php83-openssl" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php83-*, default none -# - "PHP_APKS=php-pecl-apcu php-pecl-redis" # Add php extensions, see available packages here: https://pkgs.alpinelinux.org/packages?branch=v3.19&repo=community&arch=x86_64&name=php-*, default none + +# This can be used with DISABLE_HTTP=true, to force HTTPS redirects for every host +# npmplus-caddy: +# container_name: npmplus-caddy +# image: zoeyvid/npmplus:caddy +# restart: always +# network_mode: bridge +# ports: +# - "80:80" +# environment: +# - "TZ=Europe/Berlin" diff --git a/renovate.json b/renovate.json index 0897d9b39..242772694 100644 --- a/renovate.json +++ b/renovate.json @@ -2,7 +2,7 @@ "extends": [ "config:base" ], - "baseBranches": [], + "baseBranches": ["develop", "php"], "includeForks": true, "automerge": false, "branchPrefix": "renovate-deps-update-", diff --git a/rootfs/etc/tls/dhparam b/rootfs/etc/dhparam similarity index 100% rename from rootfs/etc/tls/dhparam rename to rootfs/etc/dhparam diff --git a/rootfs/etc/tls/certbot.ini b/rootfs/etc/tls/certbot.ini deleted file mode 100644 index a9a0f9092..000000000 --- a/rootfs/etc/tls/certbot.ini +++ /dev/null @@ -1,15 +0,0 @@ -agree-tos = true -non-interactive = true -webroot-path = /tmp/acme-challenge - -new-key= true -key-type = ecdsa -must-staple = true -no-reuse-key = true -rsa-key-size = 4096 -elliptic-curve = secp384r1 - -# An example of using an alternate ACME server that uses EAB credentials -# server = https://dv.acme-v02.api.pki.goog/directory -# eab-kid = somestringofstuffwithoutquotes -# eab-hmac-key = yaddayaddahexhexnotquoted diff --git a/rootfs/html/404/bootstrap.min.css b/rootfs/html/404/bootstrap.min.css deleted file mode 100644 index ed3905e0e..000000000 --- a/rootfs/html/404/bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/rootfs/html/404/index.html b/rootfs/html/404/index.html deleted file mode 100644 index 377251c13..000000000 --- a/rootfs/html/404/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - 404 Not Found - - - - -
-
-

404 Not Found

-
-

- Powered by NPMplus - -

-
- - diff --git a/rootfs/html/default/bootstrap.min.css b/rootfs/html/default/bootstrap.min.css deleted file mode 100644 index ed3905e0e..000000000 --- a/rootfs/html/default/bootstrap.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/rootfs/html/default/index.html b/rootfs/html/default/index.html deleted file mode 100644 index 64f6a6a27..000000000 --- a/rootfs/html/default/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - Default Site - - - - -
-
-

Congratulations!

-

You've successfully started NPMplus.

-

If you're seeing this site then you're trying to access a host that isn't set up yet.

-

Log in to the Admin panel to get started.

-
-

- Powered by NPMplus - -

-
- - diff --git a/rootfs/usr/local/bin/healthcheck.sh b/rootfs/usr/local/bin/healthcheck.sh index c234aa791..c168b0607 100755 --- a/rootfs/usr/local/bin/healthcheck.sh +++ b/rootfs/usr/local/bin/healthcheck.sh @@ -31,7 +31,7 @@ if [ "$GOA_IPV6_BINDING" != "[::]" ] && [ "$GOA_IPV4_BINDING" != "0.0.0.0" ]; th fi fi -if (if [ "$GOA" = "true" ]; then [ -f /tmp/goa/index.html ] && nc -z "$HCGOA_IP" "$GOA_PORT"; fi && if [ "$PHP81" = true ]; then cgi-fcgi -bind -connect /run/php81.sock > /dev/null 2>&1; fi && if [ "$PHP82" = true ]; then cgi-fcgi -bind -connect /run/php82.sock > /dev/null 2>&1; fi && if [ "$PHP83" = true ]; then cgi-fcgi -bind -connect /run/php83.sock > /dev/null 2>&1; fi && [ "$(curl -sk https://"$HCNPM_IP":"$NPM_PORT"/api/ | jq --raw-output .status)" = "OK" ]); then +if (if [ "$GOA" = "true" ]; then [ -f /tmp/goa/index.html ] && nc -z "$HCGOA_IP" "$GOA_PORT"; fi && if [ "$PHP81" = true ]; then cgi-fcgi -bind -connect /run/php81.sock > /dev/null 2>&1; fi && if [ "$PHP82" = true ]; then cgi-fcgi -bind -connect /run/php82.sock > /dev/null 2>&1; fi && cgi-fcgi -bind -connect /run/php83.sock > /dev/null 2>&1 && [ "$(curl -sk https://"$HCNPM_IP":"$NPM_PORT"/status | jq -r .status)" = "ok" ]); then echo "OK" exit 0 else diff --git a/rootfs/usr/local/bin/launch.sh b/rootfs/usr/local/bin/launch.sh index 3294bc57b..851544e02 100755 --- a/rootfs/usr/local/bin/launch.sh +++ b/rootfs/usr/local/bin/launch.sh @@ -36,19 +36,18 @@ if [ "$PHP82" = "true" ]; then fi fi -if [ "$PHP83" = "true" ]; then - if ! PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt > /dev/null 2>&1; then - PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt - sleep inf - fi +if ! PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt > /dev/null 2>&1; then + PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FORt + sleep inf fi +nginx -e stderr & if [ "$PHP81" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/81/conf.d php-fpm81 -c /data/php/81 -y /data/php/81/php-fpm.conf -FOR; fi & if [ "$PHP82" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/82/conf.d php-fpm82 -c /data/php/82 -y /data/php/82/php-fpm.conf -FOR; fi & -if [ "$PHP83" = "true" ]; then PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FOR; fi & +PHP_INI_SCAN_DIR=/data/php/83/conf.d php-fpm83 -c /data/php/83 -y /data/php/83/php-fpm.conf -FOR & if [ "$LOGROTATE" = "true" ]; then while true; do logrotate --verbose --state /data/etc/logrotate.status /etc/logrotate; sleep 25h; done; fi & # shellcheck disable=SC2086 if [ "$GOA" = "true" ]; then while true; do goaccess --no-global-config --num-tests=0 --tz="$TZ" --date-format="%d/%b/%Y" --time-format="%H:%M:%S" --log-format='[%d:%t %^] %v %h %T "%r" %s %b %b %R %u' --no-ip-validation --addr=127.0.0.1 --port="$GOAIWSP" \ -f /data/nginx/access.log --real-time-html -o /tmp/goa/index.html --persist --restore --db-path=/data/etc/goaccess/data -b /etc/goaccess/browsers.list -b /etc/goaccess/podcast.list $GOACLA; done; fi & -aio.sh & +#aio.sh & index.js diff --git a/rootfs/usr/local/bin/start.sh b/rootfs/usr/local/bin/start.sh index 8c5dcb829..8b799446a 100755 --- a/rootfs/usr/local/bin/start.sh +++ b/rootfs/usr/local/bin/start.sh @@ -51,11 +51,6 @@ if ! echo "$PGID" | grep -q "^[0-9]\+$"; then sleep inf fi -if ! echo "$NIBEP" | grep -q "^[0-9]\+$"; then - echo "NIBEP needs to be a number." - sleep inf -fi - if ! echo "$GOAIWSP" | grep -q "^[0-9]\+$"; then echo "GOAIWSP needs to be a number." sleep inf @@ -221,6 +216,11 @@ if [ -n "$PHP82_APKS" ] && ! echo "$PHP82_APKS" | grep -q "^[a-z0-9 _-]\+$"; the sleep inf fi +if [ -n "$PHP83_APKS" ] && ! echo "$PHP83_APKS" | grep -q "^[a-z0-9 _-]\+$"; then + echo "PHP83_APKS can consist of lower letters a-z, numbers 0-9, spaces, underscores and hyphens." + sleep inf +fi + if [ -n "$NC_AIO" ] && ! echo "$NC_AIO" | grep -q "^true$\|^false$"; then echo "NC_AIO needs to be true or false." @@ -263,28 +263,21 @@ if [ -s /data/etc/goaccess/geoip/GeoLite2-Country.mmdb ] && [ -s /data/etc/goacc fi -if [ "$PHP81" = "true" ] || [ "$PHP82" = "true" ] || [ "$PHP83" = "true" ]; then - - apk add --no-cache fcgi - - # From https://github.com/nextcloud/all-in-one/pull/1377/files - if [ -n "$PHP_APKS" ]; then - for apk in $(echo "$PHP_APKS" | tr " " "\n"); do - - if ! echo "$apk" | grep -q "^php-.*$"; then - echo "$apk is a non allowed value." - echo "It needs to start with \"php-\"." - echo "It is set to \"$apk\"." - sleep inf - fi - - echo "Installing $apk via apk..." - if ! apk add --no-cache "$apk" > /dev/null 2>&1; then - echo "The apk \"$apk\" was not installed!" - fi +# From https://github.com/nextcloud/all-in-one/pull/1377/files +if [ -n "$PHP_APKS" ]; then + for apk in $(echo "$PHP_APKS" | tr " " "\n"); do + if ! echo "$apk" | grep -q "^php-.*$"; then + echo "$apk is a non allowed value." + echo "It needs to start with \"php-\"." + echo "It is set to \"$apk\"." + sleep inf + fi - done - fi + echo "Installing $apk via apk..." + if ! apk add --no-cache "$apk" > /dev/null 2>&1; then + echo "The apk \"$apk\" was not installed!" + fi + done fi if [ "$PHP81" = "true" ]; then @@ -294,7 +287,6 @@ if [ "$PHP81" = "true" ]; then # From https://github.com/nextcloud/all-in-one/pull/1377/files if [ -n "$PHP81_APKS" ]; then for apk in $(echo "$PHP81_APKS" | tr " " "\n"); do - if ! echo "$apk" | grep -q "^php81-.*$"; then echo "$apk is a non allowed value." echo "It needs to start with \"php81-\"." @@ -306,7 +298,6 @@ if [ "$PHP81" = "true" ]; then if ! apk add --no-cache "$apk" > /dev/null 2>&1; then echo "The apk \"$apk\" was not installed!" fi - done fi @@ -327,7 +318,6 @@ if [ "$PHP82" = "true" ]; then # From https://github.com/nextcloud/all-in-one/pull/1377/files if [ -n "$PHP82_APKS" ]; then for apk in $(echo "$PHP82_APKS" | tr " " "\n"); do - if ! echo "$apk" | grep -q "^php82-.*$"; then echo "$apk is a non allowed value." echo "It needs to start with \"php82-\"." @@ -339,7 +329,6 @@ if [ "$PHP82" = "true" ]; then if ! apk add --no-cache "$apk" > /dev/null 2>&1; then echo "The apk \"$apk\" was not installed!" fi - done fi @@ -353,38 +342,27 @@ elif [ "$FULLCLEAN" = "true" ]; then rm -vrf /data/php/82 fi -if [ "$PHP83" = "true" ]; then - - apk add --no-cache php83-fpm - - # From https://github.com/nextcloud/all-in-one/pull/1377/files - if [ -n "$PHP83_APKS" ]; then - for apk in $(echo "$PHP83_APKS" | tr " " "\n"); do - - if ! echo "$apk" | grep -q "^php83-.*$"; then - echo "$apk is a non allowed value." - echo "It needs to start with \"php83-\"." - echo "It is set to \"$apk\"." - sleep inf - fi - - echo "Installing $apk via apk..." - if ! apk add --no-cache "$apk" > /dev/null 2>&1; then - echo "The apk \"$apk\" was not installed!" - fi - - done - fi - - mkdir -vp /data/php - cp -varnT /etc/php83 /data/php/83 - sed -i "s|listen =.*|listen = /run/php83.sock|" /data/php/83/php-fpm.d/www.conf - sed -i "s|;error_log =.*|error_log = /proc/self/fd/2|g" /data/php/83/php-fpm.conf - sed -i "s|include=.*|include=/data/php/83/php-fpm.d/*.conf|g" /data/php/83/php-fpm.conf +# From https://github.com/nextcloud/all-in-one/pull/1377/files +if [ -n "$PHP83_APKS" ]; then + for apk in $(echo "$PHP83_APKS" | tr " " "\n"); do + if ! echo "$apk" | grep -q "^php83-.*$"; then + echo "$apk is a non allowed value." + echo "It needs to start with \"php83-\"." + echo "It is set to \"$apk\"." + sleep inf + fi -elif [ "$FULLCLEAN" = "true" ]; then - rm -vrf /data/php/83 + echo "Installing $apk via apk..." + if ! apk add --no-cache "$apk" > /dev/null 2>&1; then + echo "The apk \"$apk\" was not installed!" + fi + done fi +mkdir -vp /data/php +cp -varnT /etc/php83 /data/php/83 +sed -i "s|listen =.*|listen = /run/php83.sock|" /data/php/83/php-fpm.d/www.conf +sed -i "s|;error_log =.*|error_log = /proc/self/fd/2|g" /data/php/83/php-fpm.conf +sed -i "s|include=.*|include=/data/php/83/php-fpm.d/*.conf|g" /data/php/83/php-fpm.conf if [ "$LOGROTATE" = "true" ]; then apk add --no-cache logrotate diff --git a/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf b/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf index 698e2cce7..49236ed77 100644 --- a/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf +++ b/rootfs/usr/local/nginx/conf/conf.d/include/tls-ciphers.conf @@ -6,7 +6,7 @@ ssl_stapling_verify on; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off; -ssl_dhparam /etc/tls/dhparam; +ssl_dhparam /etc/dhparam; # intermediate configuration. tweak to your needs. ssl_protocols TLSv1.2 TLSv1.3; diff --git a/rootfs/usr/local/nginx/conf/conf.d/npm.conf b/rootfs/usr/local/nginx/conf/conf.d/npm.conf deleted file mode 100644 index 1898765b0..000000000 --- a/rootfs/usr/local/nginx/conf/conf.d/npm.conf +++ /dev/null @@ -1,36 +0,0 @@ -server { - http3 off; - listen 81 ssl default_server; - listen [::]:81 ssl default_server; - - server_name _; - include conf.d/include/brotli.conf; - include conf.d/include/force-tls.conf; - include conf.d/include/tls-ciphers.conf; - include conf.d/include/block-exploits.conf; - - modsecurity on; - modsecurity_rules_file /usr/local/nginx/conf/conf.d/include/modsecurity.conf; - - #ssl_certificate ; - #ssl_certificate_key ; - #ssl_trusted_certificate ; - - location /api { - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - include conf.d/include/proxy-location.conf; - - rewrite ^/api(/.*)$ $1 break; - proxy_pass http://127.0.0.1:48693; - } - - location / { - root /html/frontend; - if ($request_uri ~ ^/(.*)\.html$) { - return 302 /$1; - } - try_files $uri $uri.html $uri/ /index.html; - } -} diff --git a/rootfs/usr/local/nginx/conf/conf.d/npm-no-server-name.conf b/rootfs/usr/local/nginx/conf/conf.d/npmplus-no-server-name.conf similarity index 100% rename from rootfs/usr/local/nginx/conf/conf.d/npm-no-server-name.conf rename to rootfs/usr/local/nginx/conf/conf.d/npmplus-no-server-name.conf diff --git a/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf new file mode 100644 index 000000000..ed5cd4ce2 --- /dev/null +++ b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf @@ -0,0 +1,27 @@ +server { + http3 off; + listen 81 ssl default_server; + listen [::]:81 ssl default_server; + + server_name _; + include conf.d/include/brotli.conf; + include conf.d/include/force-tls.conf; + include conf.d/include/tls-ciphers.conf; + include conf.d/include/block-exploits.conf; + + #ssl_certificate ; + #ssl_certificate_key ; + #ssl_trusted_certificate ; + + location / { + alias /html/app/public/; + + location ~ [^/]\.php(/|$) { + fastcgi_pass php83; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + if (!-f $document_root$fastcgi_script_name) { + return 404; + } + } + } +} diff --git a/src/composer.json b/src/composer.json new file mode 100644 index 000000000..c185d5b02 --- /dev/null +++ b/src/composer.json @@ -0,0 +1,37 @@ +{ + "name": "zoeyvid/npmplus", + "description": "WebUI for NPMplus, which manages nginx and acme.sh", + "type": "project", + "require": { + "phpmailer/phpmailer": "6.9.1", + "chillerlan/php-qrcode": "5.0.2" + }, + "license": "AGPL-3.0", + "version": "v0.0.1-alpha", + "authors": [ + { + "name": "Zoey", + "email": "zoey@z0ey.de", + "homepage": "https://z0ey.de" + }, + { + "name": "David", + "email": "david@davidcraft.de", + "homepage": "https://davidcraft.de" + }, + { + "name": "ZoeyVid", + "email": "zoeyvid@zvcdn.de", + "homepage": "https://zoeyvid.de" + } + ], + "minimum-stability": "alpha", + "support": { + "email": "zoey@z0ey.de", + "issues": "https://github.com/ZoeyVid/booking/issues", + "forum": "https://github.com/ZoeyVid/booking/discussions", + "wiki": "https://github.com/ZoeyVid/booking", + "source": "https://github.com/ZoeyVid/booking", + "docs": "https://github.com/ZoeyVid/booking" + } +} diff --git a/src/composer.lock b/src/composer.lock new file mode 100644 index 000000000..6d556524b --- /dev/null +++ b/src/composer.lock @@ -0,0 +1,259 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d2ab1ff203e9fa048bf2f3325f44d777", + "packages": [ + { + "name": "chillerlan/php-qrcode", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-qrcode.git", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/da5bdb82c8755f54de112b271b402aaa8df53269", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269", + "shasum": "" + }, + "require": { + "chillerlan/php-settings-container": "^2.1.4 || ^3.1", + "ext-mbstring": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name": "ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "keywords": [ + "phpqrcode", + "qr", + "qr code", + "qr-reader", + "qrcode", + "qrcode-generator", + "qrcode-reader" + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-02-27T14:37:26+00:00" + }, + { + "name": "chillerlan/php-settings-container", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "keywords": [ + "Settings", + "configuration", + "container", + "helper" + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-03-02T20:07:15+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2023-11-25T22:23:28+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "alpha", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/security.txt b/src/public/.well-known/security.txt similarity index 100% rename from security.txt rename to src/public/.well-known/security.txt diff --git a/src/public/status/index.php b/src/public/status/index.php new file mode 100644 index 000000000..5e34bd396 --- /dev/null +++ b/src/public/status/index.php @@ -0,0 +1,2 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/chillerlan/php-qrcode/NOTICE b/src/vendor/chillerlan/php-qrcode/NOTICE new file mode 100644 index 000000000..596130bf7 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/NOTICE @@ -0,0 +1,40 @@ +Parts of this code are ported to php from the ZXing project +and licensed under the Apache License, Version 2.0. + +Copyright 2007 ZXing authors (https://github.com/zxing/zxing), +Copyright (c) Ashot Khanamiryan (https://github.com/khanamiryan/php-qrcode-detector-decoder) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +List of affected files: + +src/Common/ECICharset.php +src/Common/GenericGFPoly.php +src/Common/GF256.php +src/Common/LuminanceSourceAbstract.php +src/Common/MaskPattern.php +src/Decoder/Binarizer.php +src/Decoder/BitMatrix.php +src/Decoder/Decoder.php +src/Decoder/DecoderResult.php +src/Decoder/ReedSolomonDecoder.php +src/Detector/AlignmentPattern.php +src/Detector/AlignmentPatternFinder.php +src/Detector/Detector.php +src/Detector/FinderPattern.php +src/Detector/FinderPatternFinder.php +src/Detector/GridSampler.php +src/Detector/PerspectiveTransform.php +src/Detector/ResultPoint.php +tests/Common/MaskPatternTest.php diff --git a/src/vendor/chillerlan/php-qrcode/README.md b/src/vendor/chillerlan/php-qrcode/README.md new file mode 100644 index 000000000..9aaaaea3c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/README.md @@ -0,0 +1,168 @@ +# chillerlan/php-qrcode + +A PHP QR Code generator based on the [implementation by Kazuhiko Arase](https://github.com/kazuhikoarase/qrcode-generator), namespaced, cleaned up, improved and other stuff.
+It also features a QR Code reader based on a [PHP port](https://github.com/khanamiryan/php-qrcode-detector-decoder) of the [ZXing library](https://github.com/zxing/zxing). + +**Attention:** there is now also a javascript port: [chillerlan/js-qrcode](https://github.com/chillerlan/js-qrcode). + +[![PHP Version Support][php-badge]][php] +[![Packagist version][packagist-badge]][packagist] +[![Continuous Integration][gh-action-badge]][gh-action] +[![CodeCov][coverage-badge]][coverage] +[![Codacy][codacy-badge]][codacy] +[![Packagist downloads][downloads-badge]][downloads] +[![Documentation][readthedocs-badge]][readthedocs] + +[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF +[php]: https://www.php.net/supported-versions.php +[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist +[packagist]: https://packagist.org/packages/chillerlan/php-qrcode +[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/ci.yml?branch=v5.0.x&logo=github +[gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/ci.yml?query=branch%3Amain +[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v5.0.x?logo=codecov +[coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v5.0.x +[codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v5.0.x?logo=codacy +[codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v5.0.x +[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist +[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats +[readthedocs-badge]: https://img.shields.io/readthedocs/php-qrcode/v5.0.x?logo=readthedocs +[readthedocs]: https://php-qrcode.readthedocs.io/en/v5.0.x/ + +## Overview + +### Features + +- Creation of [Model 2 QR Codes](https://www.qrcode.com/en/codes/model12.html), [Version 1 to 40](https://www.qrcode.com/en/about/version.html) +- [ECC Levels](https://www.qrcode.com/en/about/error_correction.html) L/M/Q/H supported +- Mixed mode support (encoding modes can be combined within a QR symbol). Supported modes: + - numeric + - alphanumeric + - 8-bit binary + - [ECI support](https://en.wikipedia.org/wiki/Extended_Channel_Interpretation) + - 13-bit double-byte: + - kanji (Japanese, Shift-JIS) + - hanzi (simplified Chinese, GB2312/GB18030) as [defined in GBT18284-2000](https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000) +- Flexible, easily extensible output modules, built-in support for the following output formats: + - [GdImage](https://www.php.net/manual/book.image) (raster graphics: bmp, gif, jpeg, png, webp) + - [ImageMagick](https://www.php.net/manual/book.imagick) ([multiple supported image formats](https://imagemagick.org/script/formats.php)) + - Markup types: SVG, HTML, etc. + - String types: JSON, plain text, etc. + - Encapsulated Postscript (EPS) + - PDF via [FPDF](https://github.com/setasign/fpdf) +- QR Code reader (via GD and ImageMagick) + + +### Requirements + +- PHP 7.4+ + - [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php) + - optional: + - [`ext-gd`](https://www.php.net/manual/book.image) + - [`ext-imagick`](https://github.com/Imagick/imagick) with [ImageMagick](https://imagemagick.org) installed + - [`ext-fileinfo`](https://www.php.net/manual/book.fileinfo.php) (required by `QRImagick` output) + - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module + +For the QRCode reader, either `ext-gd` or `ext-imagick` is required! + + +## Documentation + +- The user manual is at https://php-qrcode.readthedocs.io/ ([sources](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs)) +- An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/ +- The documentation for the `QROptions` container can be found here: [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container#readme) + + +## Installation with [composer](https://getcomposer.org) + +See [the installation guide](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Installation.html) for more info! + + +### Terminal + +``` +composer require chillerlan/php-qrcode +``` + + +### composer.json + +```json +{ + "require": { + "php": "^7.4 || ^8.0", + "chillerlan/php-qrcode": "v5.0.x-dev#" + } +} +``` + +Note: replace `v5.0.x-dev` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^4.3` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions. + + +## Quickstart + +We want to encode this URI for a mobile authenticator into a QRcode image: + +```php +$data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net'; + +// quick and simple: +echo 'QR Code'; +``` + +Wait, what was that? Please again, slower! See [Advanced usage](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Advanced-usage.html) in the manual. +Also, have a look [in the examples folder](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/examples) for some more usage examples. + +

+ QR codes are awesome! +

+ + +### Reading QR Codes + +Using the built-in QR Code reader is pretty straight-forward: + +```php +// it's generally a good idea to wrap the reader in a try/catch block because it WILL throw eventually +try{ + $result = (new QRCode)->readFromFile('path/to/file.png'); // -> DecoderResult + + // you can now use the result instance... + $content = $result->data; + $matrix = $result->getMatrix(); // -> QRMatrix + + // ...or simply cast it to string to get the content: + $content = (string)$result; +} +catch(Throwable $e){ + // oopsies! +} +``` + + +## Shameless advertising + +Hi, please check out some of my other projects that are way cooler than qrcodes! + +- [js-qrcode](https://github.com/chillerlan/js-qrcode) - a javascript port of this library +- [php-authenticator](https://github.com/chillerlan/php-authenticator) - a Google Authenticator implementation (see [authenticator example](https://github.com/chillerlan/php-qrcode/blob/v5.0.x/examples/authenticator.php)) +- [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation +- [php-oauth-core](https://github.com/chillerlan/php-oauth-core) - an OAuth 1/2 client library along with a bunch of [providers](https://github.com/chillerlan/php-oauth-providers) +- [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird +- [php-tootbot](https://github.com/php-tootbot/tootbot-template) - a Mastodon bot library (see [@dwil](https://github.com/php-tootbot/dwil)) + + +## Disclaimer! + +I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk! + + +### License notice + +- Parts of this code are [ported to PHP](https://github.com/codemasher/php-qrcode-decoder) from the [ZXing project](https://github.com/zxing/zxing) and licensed under the [Apache License, Version 2.0](./NOTICE). +- [The documentation](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/docs) is licensed under the [Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/). + + +### Trademark Notice + +The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*
+https://www.qrcode.com/en/faq.html#patentH2Title diff --git a/src/vendor/chillerlan/php-qrcode/composer.json b/src/vendor/chillerlan/php-qrcode/composer.json new file mode 100644 index 000000000..7e74be558 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/composer.json @@ -0,0 +1,79 @@ +{ + "name": "chillerlan/php-qrcode", + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "license": [ + "MIT", "Apache-2.0" + ], + "type": "library", + "keywords": [ + "QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode", "qrcode-reader", "qr-reader" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name":"ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage":"https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": "^7.4 || ^8.0", + "ext-mbstring": "*", + "chillerlan/php-settings-container": "^2.1.4 || ^3.1" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpunit/phpunit": "^9.6", + "phpmd/phpmd": "^2.15", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "chillerlan\\QRCodeTest\\": "tests/" + } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phan": "@php vendor/bin/phan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true + } +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php b/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php new file mode 100644 index 000000000..4a59f2b58 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php @@ -0,0 +1,180 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function count, floor, min; + +/** + * Holds the raw binary data + */ +final class BitBuffer{ + + /** + * The buffer content + * + * @var int[] + */ + private array $buffer; + + /** + * Length of the content (bits) + */ + private int $length; + + /** + * Read count (bytes) + */ + private int $bytesRead = 0; + + /** + * Read count (bits) + */ + private int $bitsRead = 0; + + /** + * BitBuffer constructor. + * + * @param int[] $bytes + */ + public function __construct(array $bytes = []){ + $this->buffer = $bytes; + $this->length = count($this->buffer); + } + + /** + * appends a sequence of bits + */ + public function put(int $bits, int $length):self{ + + for($i = 0; $i < $length; $i++){ + $this->putBit((($bits >> ($length - $i - 1)) & 1) === 1); + } + + return $this; + } + + /** + * appends a single bit + */ + public function putBit(bool $bit):self{ + $bufIndex = (int)floor($this->length / 8); + + if(count($this->buffer) <= $bufIndex){ + $this->buffer[] = 0; + } + + if($bit === true){ + $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8)); + } + + $this->length++; + + return $this; + } + + /** + * returns the current buffer length + */ + public function getLength():int{ + return $this->length; + } + + /** + * returns the buffer content + * + * to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer()) + */ + public function getBuffer():array{ + return $this->buffer; + } + + /** + * @return int number of bits that can be read successfully + */ + public function available():int{ + return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead); + } + + /** + * @author Sean Owen, ZXing + * + * @param int $numBits number of bits to read + * + * @return int representing the bits read. The bits will appear as the least-significant bits of the int + * @throws \chillerlan\QRCode\QRCodeException if numBits isn't in [1,32] or more than is available + */ + public function read(int $numBits):int{ + + if($numBits < 1 || $numBits > $this->available()){ + throw new QRCodeException('invalid $numBits: '.$numBits); + } + + $result = 0; + + // First, read remainder from current byte + if($this->bitsRead > 0){ + $bitsLeft = (8 - $this->bitsRead); + $toRead = min($numBits, $bitsLeft); + $bitsToNotRead = ($bitsLeft - $toRead); + $mask = ((0xff >> (8 - $toRead)) << $bitsToNotRead); + $result = (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead); + $numBits -= $toRead; + $this->bitsRead += $toRead; + + if($this->bitsRead === 8){ + $this->bitsRead = 0; + $this->bytesRead++; + } + } + + // Next read whole bytes + if($numBits > 0){ + + while($numBits >= 8){ + $result = (($result << 8) | ($this->buffer[$this->bytesRead] & 0xff)); + $this->bytesRead++; + $numBits -= 8; + } + + // Finally read a partial byte + if($numBits > 0){ + $bitsToNotRead = (8 - $numBits); + $mask = ((0xff >> $bitsToNotRead) << $bitsToNotRead); + $result = (($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead)); + $this->bitsRead += $numBits; + } + } + + return $result; + } + + /** + * Clears the buffer and resets the stats + */ + public function clear():self{ + $this->buffer = []; + $this->length = 0; + + return $this->rewind(); + } + + /** + * Resets the read-counters + */ + public function rewind():self{ + $this->bytesRead = 0; + $this->bitsRead = 0; + + return $this; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php b/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php new file mode 100644 index 000000000..0c98e36da --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php @@ -0,0 +1,125 @@ + + * @copyright 2021 smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function sprintf; + +/** + * ISO/IEC 18004:2000 - 8.4.1 Extended Channel Interpretation (ECI) Mode + */ +final class ECICharset{ + + public const CP437 = 0; // Code page 437, DOS Latin US + public const ISO_IEC_8859_1_GLI = 1; // GLI encoding with characters 0 to 127 identical to ISO/IEC 646 and characters 128 to 255 identical to ISO 8859-1 + public const CP437_WO_GLI = 2; // An equivalent code table to CP437, without the return-to-GLI 0 logic + public const ISO_IEC_8859_1 = 3; // Latin-1 (Default) + public const ISO_IEC_8859_2 = 4; // Latin-2 + public const ISO_IEC_8859_3 = 5; // Latin-3 + public const ISO_IEC_8859_4 = 6; // Latin-4 + public const ISO_IEC_8859_5 = 7; // Latin/Cyrillic + public const ISO_IEC_8859_6 = 8; // Latin/Arabic + public const ISO_IEC_8859_7 = 9; // Latin/Greek + public const ISO_IEC_8859_8 = 10; // Latin/Hebrew + public const ISO_IEC_8859_9 = 11; // Latin-5 + public const ISO_IEC_8859_10 = 12; // Latin-6 + public const ISO_IEC_8859_11 = 13; // Latin/Thai + // 14 reserved + public const ISO_IEC_8859_13 = 15; // Latin-7 (Baltic Rim) + public const ISO_IEC_8859_14 = 16; // Latin-8 (Celtic) + public const ISO_IEC_8859_15 = 17; // Latin-9 + public const ISO_IEC_8859_16 = 18; // Latin-10 + // 19 reserved + public const SHIFT_JIS = 20; // JIS X 0208 Annex 1 + JIS X 0201 + public const WINDOWS_1250_LATIN_2 = 21; // Superset of Latin-2, Central Europe + public const WINDOWS_1251_CYRILLIC = 22; // Latin/Cyrillic + public const WINDOWS_1252_LATIN_1 = 23; // Superset of Latin-1 + public const WINDOWS_1256_ARABIC = 24; + public const ISO_IEC_10646_UCS_2 = 25; // High order byte first (UTF-16BE) + public const ISO_IEC_10646_UTF_8 = 26; // UTF-8 + public const ISO_IEC_646_1991 = 27; // International Reference Version of ISO 7-bit coded character set (US-ASCII) + public const BIG5 = 28; // Big 5 (Taiwan) Chinese Character Set + public const GB18030 = 29; // GB (PRC) Chinese Character Set + public const EUC_KR = 30; // Korean Character Set + + /** + * map of charset id -> name + * + * @see \mb_list_encodings() + */ + public const MB_ENCODINGS = [ + self::CP437 => null, + self::ISO_IEC_8859_1_GLI => null, + self::CP437_WO_GLI => null, + self::ISO_IEC_8859_1 => 'ISO-8859-1', + self::ISO_IEC_8859_2 => 'ISO-8859-2', + self::ISO_IEC_8859_3 => 'ISO-8859-3', + self::ISO_IEC_8859_4 => 'ISO-8859-4', + self::ISO_IEC_8859_5 => 'ISO-8859-5', + self::ISO_IEC_8859_6 => 'ISO-8859-6', + self::ISO_IEC_8859_7 => 'ISO-8859-7', + self::ISO_IEC_8859_8 => 'ISO-8859-8', + self::ISO_IEC_8859_9 => 'ISO-8859-9', + self::ISO_IEC_8859_10 => 'ISO-8859-10', + self::ISO_IEC_8859_11 => null, + self::ISO_IEC_8859_13 => 'ISO-8859-13', + self::ISO_IEC_8859_14 => 'ISO-8859-14', + self::ISO_IEC_8859_15 => 'ISO-8859-15', + self::ISO_IEC_8859_16 => 'ISO-8859-16', + self::SHIFT_JIS => 'SJIS', + self::WINDOWS_1250_LATIN_2 => null, // @see https://www.php.net/manual/en/function.mb-convert-encoding.php#112547 + self::WINDOWS_1251_CYRILLIC => 'Windows-1251', + self::WINDOWS_1252_LATIN_1 => 'Windows-1252', + self::WINDOWS_1256_ARABIC => null, // @see https://stackoverflow.com/a/8592995 + self::ISO_IEC_10646_UCS_2 => 'UTF-16BE', + self::ISO_IEC_10646_UTF_8 => 'UTF-8', + self::ISO_IEC_646_1991 => 'ASCII', + self::BIG5 => 'BIG-5', + self::GB18030 => 'GB18030', + self::EUC_KR => 'EUC-KR', + ]; + + /** + * The current ECI character set ID + */ + private int $charsetID; + + /** + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $charsetID){ + + if($charsetID < 0 || $charsetID > 999999){ + throw new QRCodeException(sprintf('invalid charset id: "%s"', $charsetID)); + } + + $this->charsetID = $charsetID; + } + + /** + * Returns the current character set ID + */ + public function getID():int{ + return $this->charsetID; + } + + /** + * Returns the name of the current character set or null if no name is available + * + * @see \mb_convert_encoding() + * @see \iconv() + */ + public function getName():?string{ + return (self::MB_ENCODINGS[$this->charsetID] ?? null); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php b/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php new file mode 100644 index 000000000..789d7f79d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php @@ -0,0 +1,223 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function array_column; + +/** + * This class encapsulates the four error correction levels defined by the QR code standard. + */ +final class EccLevel{ + + // ISO/IEC 18004:2000 Tables 12, 25 + + /** @var int */ + public const L = 0b01; // 7%. + /** @var int */ + public const M = 0b00; // 15%. + /** @var int */ + public const Q = 0b11; // 25%. + /** @var int */ + public const H = 0b10; // 30%. + + /** + * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40 + * + * @var int[][] + */ + private const MAX_BITS = [ + // [ L, M, Q, H] // v => modules + [ 0, 0, 0, 0], // 0 => will be ignored, index starts at 1 + [ 152, 128, 104, 72], // 1 => 21 + [ 272, 224, 176, 128], // 2 => 25 + [ 440, 352, 272, 208], // 3 => 29 + [ 640, 512, 384, 288], // 4 => 33 + [ 864, 688, 496, 368], // 5 => 37 + [ 1088, 864, 608, 480], // 6 => 41 + [ 1248, 992, 704, 528], // 7 => 45 + [ 1552, 1232, 880, 688], // 8 => 49 + [ 1856, 1456, 1056, 800], // 9 => 53 + [ 2192, 1728, 1232, 976], // 10 => 57 + [ 2592, 2032, 1440, 1120], // 11 => 61 + [ 2960, 2320, 1648, 1264], // 12 => 65 + [ 3424, 2672, 1952, 1440], // 13 => 69 NICE! + [ 3688, 2920, 2088, 1576], // 14 => 73 + [ 4184, 3320, 2360, 1784], // 15 => 77 + [ 4712, 3624, 2600, 2024], // 16 => 81 + [ 5176, 4056, 2936, 2264], // 17 => 85 + [ 5768, 4504, 3176, 2504], // 18 => 89 + [ 6360, 5016, 3560, 2728], // 19 => 93 + [ 6888, 5352, 3880, 3080], // 20 => 97 + [ 7456, 5712, 4096, 3248], // 21 => 101 + [ 8048, 6256, 4544, 3536], // 22 => 105 + [ 8752, 6880, 4912, 3712], // 23 => 109 + [ 9392, 7312, 5312, 4112], // 24 => 113 + [10208, 8000, 5744, 4304], // 25 => 117 + [10960, 8496, 6032, 4768], // 26 => 121 + [11744, 9024, 6464, 5024], // 27 => 125 + [12248, 9544, 6968, 5288], // 28 => 129 + [13048, 10136, 7288, 5608], // 29 => 133 + [13880, 10984, 7880, 5960], // 30 => 137 + [14744, 11640, 8264, 6344], // 31 => 141 + [15640, 12328, 8920, 6760], // 32 => 145 + [16568, 13048, 9368, 7208], // 33 => 149 + [17528, 13800, 9848, 7688], // 34 => 153 + [18448, 14496, 10288, 7888], // 35 => 157 + [19472, 15312, 10832, 8432], // 36 => 161 + [20528, 15936, 11408, 8768], // 37 => 165 + [21616, 16816, 12016, 9136], // 38 => 169 + [22496, 17728, 12656, 9776], // 39 => 173 + [23648, 18672, 13328, 10208], // 40 => 177 + ]; + + /** + * ISO/IEC 18004:2000 Section 8.9 - Format Information + * + * ECC level -> mask pattern + * + * @var int[][] + */ + private const FORMAT_PATTERN = [ + [ // L + 0b111011111000100, + 0b111001011110011, + 0b111110110101010, + 0b111100010011101, + 0b110011000101111, + 0b110001100011000, + 0b110110001000001, + 0b110100101110110, + ], + [ // M + 0b101010000010010, + 0b101000100100101, + 0b101111001111100, + 0b101101101001011, + 0b100010111111001, + 0b100000011001110, + 0b100111110010111, + 0b100101010100000, + ], + [ // Q + 0b011010101011111, + 0b011000001101000, + 0b011111100110001, + 0b011101000000110, + 0b010010010110100, + 0b010000110000011, + 0b010111011011010, + 0b010101111101101, + ], + [ // H + 0b001011010001001, + 0b001001110111110, + 0b001110011100111, + 0b001100111010000, + 0b000011101100010, + 0b000001001010101, + 0b000110100001100, + 0b000100000111011, + ], + ]; + + /** + * The current ECC level value + * + * L: 0b01 + * M: 0b00 + * Q: 0b11 + * H: 0b10 + */ + private int $eccLevel; + + /** + * @param int $eccLevel containing the two bits encoding a QR Code's error correction level + * + * @todo: accept string values (PHP8+) + * @see https://github.com/chillerlan/php-qrcode/discussions/160 + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $eccLevel){ + + if((0b11 & $eccLevel) !== $eccLevel){ + throw new QRCodeException('invalid ECC level'); + } + + $this->eccLevel = $eccLevel; + } + + /** + * returns the string representation of the current ECC level + */ + public function __toString():string{ + return [ + self::L => 'L', + self::M => 'M', + self::Q => 'Q', + self::H => 'H', + ][$this->eccLevel]; + } + + /** + * returns the current ECC level + */ + public function getLevel():int{ + return $this->eccLevel; + } + + /** + * returns the ordinal value of the current ECC level + * + * references to the keys of the following tables: + * + * @see \chillerlan\QRCode\Common\EccLevel::MAX_BITS + * @see \chillerlan\QRCode\Common\EccLevel::FORMAT_PATTERN + * @see \chillerlan\QRCode\Common\Version::RSBLOCKS + */ + public function getOrdinal():int{ + return [ + self::L => 0, + self::M => 1, + self::Q => 2, + self::H => 3, + ][$this->eccLevel]; + } + + /** + * returns the format pattern for the given $eccLevel and $maskPattern + */ + public function getformatPattern(MaskPattern $maskPattern):int{ + return self::FORMAT_PATTERN[$this->getOrdinal()][$maskPattern->getPattern()]; + } + + /** + * returns an array with the max bit lengths for version 1-40 and the current ECC level + * + * @return int[] + */ + public function getMaxBits():array{ + $col = array_column(self::MAX_BITS, $this->getOrdinal()); + + unset($col[0]); // remove the inavlid index 0 + + return $col; + } + + /** + * Returns the maximum bit length for the given version and current ECC level + */ + public function getMaxBitsForVersion(Version $version):int{ + return self::MAX_BITS[$version->getVersionNumber()][$this->getOrdinal()]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php b/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php new file mode 100644 index 000000000..027466f3e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php @@ -0,0 +1,97 @@ + + * @copyright 2021 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Decoder\QRCodeDecoderException; +use chillerlan\Settings\SettingsContainerInterface; +use function file_get_contents, get_resource_type, imagecolorat, imagecolorsforindex, + imagecreatefromstring, imagefilter, imagesx, imagesy, is_resource; +use const IMG_FILTER_BRIGHTNESS, IMG_FILTER_CONTRAST, IMG_FILTER_GRAYSCALE, IMG_FILTER_NEGATE, PHP_MAJOR_VERSION; + +/** + * This class is used to help decode images from files which arrive as GD Resource + * It does not support rotation. + */ +class GDLuminanceSource extends LuminanceSourceAbstract{ + + /** + * @var resource|\GdImage + */ + protected $gdImage; + + /** + * GDLuminanceSource constructor. + * + * @param resource|\GdImage $gdImage + * @param \chillerlan\Settings\SettingsContainerInterface|null $options + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function __construct($gdImage, SettingsContainerInterface $options = null){ + + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + if( + (PHP_MAJOR_VERSION >= 8 && !$gdImage instanceof \GdImage) // @todo: remove version check in v6 + || (PHP_MAJOR_VERSION < 8 && (!is_resource($gdImage) || get_resource_type($gdImage) !== 'gd')) + ){ + throw new QRCodeDecoderException('Invalid GD image source.'); // @codeCoverageIgnore + } + + parent::__construct(imagesx($gdImage), imagesy($gdImage), $options); + + $this->gdImage = $gdImage; + + if($this->options->readerGrayscale){ + imagefilter($this->gdImage, IMG_FILTER_GRAYSCALE); + } + + if($this->options->readerInvertColors){ + imagefilter($this->gdImage, IMG_FILTER_NEGATE); + } + + if($this->options->readerIncreaseContrast){ + imagefilter($this->gdImage, IMG_FILTER_BRIGHTNESS, -100); + imagefilter($this->gdImage, IMG_FILTER_CONTRAST, -100); + } + + $this->setLuminancePixels(); + } + + /** + * + */ + protected function setLuminancePixels():void{ + + for($j = 0; $j < $this->height; $j++){ + for($i = 0; $i < $this->width; $i++){ + $argb = imagecolorat($this->gdImage, $i, $j); + $pixel = imagecolorsforindex($this->gdImage, $argb); + + $this->setLuminancePixel($pixel['red'], $pixel['green'], $pixel['blue']); + } + } + + } + + /** @inheritDoc */ + public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ + return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options); + } + + /** @inheritDoc */ + public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ + return new self(imagecreatefromstring($blob), $options); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php b/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php new file mode 100644 index 000000000..d8ba0950b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GF256.php @@ -0,0 +1,154 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; + +use function array_fill; + +/** + * This class contains utility methods for performing mathematical operations over + * the Galois Fields. Operations use a given primitive polynomial in calculations. + * + * Throughout this package, elements of the GF are represented as an int + * for convenience and speed (but at the cost of memory). + * + * + * @author Sean Owen + * @author David Olivier + */ +final class GF256{ + + /** + * irreducible polynomial whose coefficients are represented by the bits of an int, + * where the least-significant bit represents the constant coefficient + */ +# private int $primitive = 0x011D; + + private const logTable = [ + 0, // the first value is never returned, index starts at 1 + 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, + 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, + 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, + 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, + 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, + 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, + 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, + 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, + 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, + 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, + 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, + 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, + 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, + 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, + 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, + 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175, + ]; + + private const expTable = [ + 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, + 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, + 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, + 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, + 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, + 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, + 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, + 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, + 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, + 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, + 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, + 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, + 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, + 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, + 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, + 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, + ]; + + /** + * Implements both addition and subtraction -- they are the same in GF(size). + * + * @return int sum/difference of a and b + */ + public static function addOrSubtract(int $a, int $b):int{ + return ($a ^ $b); + } + + /** + * @return GenericGFPoly the monomial representing coefficient * x^degree + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function buildMonomial(int $degree, int $coefficient):GenericGFPoly{ + + if($degree < 0){ + throw new QRCodeException('degree < 0'); + } + + $coefficients = array_fill(0, ($degree + 1), 0); + $coefficients[0] = $coefficient; + + return new GenericGFPoly($coefficients); + } + + /** + * @return int 2 to the power of $a in GF(size) + */ + public static function exp(int $a):int{ + + if($a < 0){ + $a += 255; + } + elseif($a >= 256){ + $a -= 255; + } + + return self::expTable[$a]; + } + + /** + * @return int base 2 log of $a in GF(size) + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function log(int $a):int{ + + if($a < 1){ + throw new QRCodeException('$a < 1'); + } + + return self::logTable[$a]; + } + + /** + * @return int multiplicative inverse of a + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function inverse(int $a):int{ + + if($a === 0){ + throw new QRCodeException('$a === 0'); + } + + return self::expTable[(256 - self::logTable[$a] - 1)]; + } + + /** + * @return int product of a and b in GF(size) + */ + public static function multiply(int $a, int $b):int{ + + if($a === 0 || $b === 0){ + return 0; + } + + return self::expTable[((self::logTable[$a] + self::logTable[$b]) % 255)]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php b/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php new file mode 100644 index 000000000..ae361b9d0 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php @@ -0,0 +1,263 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use function array_fill, array_slice, array_splice, count; + +/** + * Represents a polynomial whose coefficients are elements of a GF. + * Instances of this class are immutable. + * + * Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation. + * + * @author Sean Owen + */ +final class GenericGFPoly{ + + private array $coefficients; + + /** + * @param array $coefficients array coefficients as ints representing elements of GF(size), arranged + * from most significant (highest-power term) coefficient to the least significant + * @param int|null $degree + * + * @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this + * is not a constant polynomial (that is, it is not the monomial "0") + */ + public function __construct(array $coefficients, int $degree = null){ + $degree ??= 0; + + if(empty($coefficients)){ + throw new QRCodeException('arg $coefficients is empty'); + } + + if($degree < 0){ + throw new QRCodeException('negative degree'); + } + + $coefficientsLength = count($coefficients); + + // Leading term must be non-zero for anything except the constant polynomial "0" + $firstNonZero = 0; + + while($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] === 0){ + $firstNonZero++; + } + + $this->coefficients = [0]; + + if($firstNonZero !== $coefficientsLength){ + $this->coefficients = array_fill(0, ($coefficientsLength - $firstNonZero + $degree), 0); + + for($i = 0; $i < ($coefficientsLength - $firstNonZero); $i++){ + $this->coefficients[$i] = $coefficients[($i + $firstNonZero)]; + } + } + + } + + /** + * @return int $coefficient of x^degree term in this polynomial + */ + public function getCoefficient(int $degree):int{ + return $this->coefficients[(count($this->coefficients) - 1 - $degree)]; + } + + /** + * @return int[] + */ + public function getCoefficients():array{ + return $this->coefficients; + } + + /** + * @return int $degree of this polynomial + */ + public function getDegree():int{ + return (count($this->coefficients) - 1); + } + + /** + * @return bool true if this polynomial is the monomial "0" + */ + public function isZero():bool{ + return $this->coefficients[0] === 0; + } + + /** + * @return int evaluation of this polynomial at a given point + */ + public function evaluateAt(int $a):int{ + + if($a === 0){ + // Just return the x^0 coefficient + return $this->getCoefficient(0); + } + + $result = 0; + + foreach($this->coefficients as $c){ + // if $a === 1 just the sum of the coefficients + $result = GF256::addOrSubtract((($a === 1) ? $result : GF256::multiply($a, $result)), $c); + } + + return $result; + } + + /** + * + */ + public function multiply(GenericGFPoly $other):self{ + + if($this->isZero() || $other->isZero()){ + return new self([0]); + } + + $product = array_fill(0, (count($this->coefficients) + count($other->coefficients) - 1), 0); + + foreach($this->coefficients as $i => $aCoeff){ + foreach($other->coefficients as $j => $bCoeff){ + $product[($i + $j)] ^= GF256::multiply($aCoeff, $bCoeff); + } + } + + return new self($product); + } + + /** + * @return \chillerlan\QRCode\Common\GenericGFPoly[] [quotient, remainder] + * @throws \chillerlan\QRCode\QRCodeException + */ + public function divide(GenericGFPoly $other):array{ + + if($other->isZero()){ + throw new QRCodeException('Division by 0'); + } + + $quotient = new self([0]); + $remainder = clone $this; + + $denominatorLeadingTerm = $other->getCoefficient($other->getDegree()); + $inverseDenominatorLeadingTerm = GF256::inverse($denominatorLeadingTerm); + + while($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()){ + $scale = GF256::multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm); + $diff = ($remainder->getDegree() - $other->getDegree()); + $quotient = $quotient->addOrSubtract(GF256::buildMonomial($diff, $scale)); + $remainder = $remainder->addOrSubtract($other->multiplyByMonomial($diff, $scale)); + } + + return [$quotient, $remainder]; + + } + + /** + * + */ + public function multiplyInt(int $scalar):self{ + + if($scalar === 0){ + return new self([0]); + } + + if($scalar === 1){ + return $this; + } + + $product = array_fill(0, count($this->coefficients), 0); + + foreach($this->coefficients as $i => $c){ + $product[$i] = GF256::multiply($c, $scalar); + } + + return new self($product); + } + + /** + * @throws \chillerlan\QRCode\QRCodeException + */ + public function multiplyByMonomial(int $degree, int $coefficient):self{ + + if($degree < 0){ + throw new QRCodeException('degree < 0'); + } + + if($coefficient === 0){ + return new self([0]); + } + + $product = array_fill(0, (count($this->coefficients) + $degree), 0); + + foreach($this->coefficients as $i => $c){ + $product[$i] = GF256::multiply($c, $coefficient); + } + + return new self($product); + } + + /** + * + */ + public function mod(GenericGFPoly $other):self{ + + if((count($this->coefficients) - count($other->coefficients)) < 0){ + return $this; + } + + $ratio = (GF256::log($this->coefficients[0]) - GF256::log($other->coefficients[0])); + + foreach($other->coefficients as $i => $c){ + $this->coefficients[$i] ^= GF256::exp(GF256::log($c) + $ratio); + } + + return (new self($this->coefficients))->mod($other); + } + + /** + * + */ + public function addOrSubtract(GenericGFPoly $other):self{ + + if($this->isZero()){ + return $other; + } + + if($other->isZero()){ + return $this; + } + + $smallerCoefficients = $this->coefficients; + $largerCoefficients = $other->coefficients; + + if(count($smallerCoefficients) > count($largerCoefficients)){ + $temp = $smallerCoefficients; + $smallerCoefficients = $largerCoefficients; + $largerCoefficients = $temp; + } + + $sumDiff = array_fill(0, count($largerCoefficients), 0); + $lengthDiff = (count($largerCoefficients) - count($smallerCoefficients)); + // Copy high-order terms only found in higher-degree polynomial's coefficients + array_splice($sumDiff, 0, $lengthDiff, array_slice($largerCoefficients, 0, $lengthDiff)); + + $countLargerCoefficients = count($largerCoefficients); + + for($i = $lengthDiff; $i < $countLargerCoefficients; $i++){ + $sumDiff[$i] = GF256::addOrSubtract($smallerCoefficients[($i - $lengthDiff)], $largerCoefficients[$i]); + } + + return new self($sumDiff); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php b/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php new file mode 100644 index 000000000..d4f66c5c2 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php @@ -0,0 +1,78 @@ + + * @copyright 2021 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\Settings\SettingsContainerInterface; +use Imagick; +use function count; + +/** + * This class is used to help decode images from files which arrive as Imagick Resource + * It does not support rotation. + */ +class IMagickLuminanceSource extends LuminanceSourceAbstract{ + + protected Imagick $imagick; + + /** + * IMagickLuminanceSource constructor. + */ + public function __construct(Imagick $imagick, SettingsContainerInterface $options = null){ + parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options); + + $this->imagick = $imagick; + + if($this->options->readerGrayscale){ + $this->imagick->setImageColorspace(Imagick::COLORSPACE_GRAY); + } + + if($this->options->readerInvertColors){ + $this->imagick->negateImage($this->options->readerGrayscale); + } + + if($this->options->readerIncreaseContrast){ + for($i = 0; $i < 10; $i++){ + $this->imagick->contrastImage(false); // misleading docs + } + } + + $this->setLuminancePixels(); + } + + /** + * + */ + protected function setLuminancePixels():void{ + $pixels = $this->imagick->exportImagePixels(1, 1, $this->width, $this->height, 'RGB', Imagick::PIXEL_CHAR); + $count = count($pixels); + + for($i = 0; $i < $count; $i += 3){ + $this->setLuminancePixel(($pixels[$i] & 0xff), ($pixels[($i + 1)] & 0xff), ($pixels[($i + 2)] & 0xff)); + } + } + + /** @inheritDoc */ + public static function fromFile(string $path, SettingsContainerInterface $options = null):self{ + return new self(new Imagick(self::checkFile($path)), $options); + } + + /** @inheritDoc */ + public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{ + $im = new Imagick; + $im->readImageBlob($blob); + + return new self($im, $options); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php new file mode 100644 index 000000000..432a6e0d4 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php @@ -0,0 +1,104 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Decoder\QRCodeDecoderException; +use chillerlan\QRCode\QROptions; +use chillerlan\Settings\SettingsContainerInterface; +use function array_slice, array_splice, file_exists, is_file, is_readable, realpath; + +/** + * The purpose of this class hierarchy is to abstract different bitmap implementations across + * platforms into a standard interface for requesting greyscale luminance values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{ + + /** @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface */ + protected SettingsContainerInterface $options; + protected array $luminances; + protected int $width; + protected int $height; + + /** + * + */ + public function __construct(int $width, int $height, SettingsContainerInterface $options = null){ + $this->width = $width; + $this->height = $height; + $this->options = ($options ?? new QROptions); + + $this->luminances = []; + } + + /** @inheritDoc */ + public function getLuminances():array{ + return $this->luminances; + } + + /** @inheritDoc */ + public function getWidth():int{ + return $this->width; + } + + /** @inheritDoc */ + public function getHeight():int{ + return $this->height; + } + + /** @inheritDoc */ + public function getRow(int $y):array{ + + if($y < 0 || $y >= $this->getHeight()){ + throw new QRCodeDecoderException('Requested row is outside the image: '.$y); + } + + $arr = []; + + array_splice($arr, 0, $this->width, array_slice($this->luminances, ($y * $this->width), $this->width)); + + return $arr; + } + + /** + * + */ + protected function setLuminancePixel(int $r, int $g, int $b):void{ + $this->luminances[] = ($r === $g && $g === $b) + // Image is already greyscale, so pick any channel. + ? $r // (($r + 128) % 256) - 128; + // Calculate luminance cheaply, favoring green. + : (($r + 2 * $g + $b) / 4); // (((($r + 2 * $g + $b) / 4) + 128) % 256) - 128; + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + protected static function checkFile(string $path):string{ + $path = trim($path); + + if(!file_exists($path) || !is_file($path) || !is_readable($path)){ + throw new QRCodeDecoderException('invalid file: '.$path); + } + + $realpath = realpath($path); + + if($realpath === false){ + throw new QRCodeDecoderException('unable to resolve path: '.$path); + } + + return $realpath; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php new file mode 100644 index 000000000..64409e36a --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php @@ -0,0 +1,61 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +/** + */ +interface LuminanceSourceInterface{ + + /** + * Fetches luminance data for the underlying bitmap. Values should be fetched using: + * `int luminance = array[y * width + x] & 0xff` + * + * @return array A row-major 2D array of luminance values. Do not use result $length as it may be + * larger than $width * $height bytes on some platforms. Do not modify the contents + * of the result. + */ + public function getLuminances():array; + + /** + * @return int The width of the bitmap. + */ + public function getWidth():int; + + /** + * @return int The height of the bitmap. + */ + public function getHeight():int; + + /** + * Fetches one row of luminance data from the underlying platform's bitmap. Values range from + * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have + * to bitwise and with 0xff for each value. It is preferable for implementations of this method + * to only fetch this row rather than the whole image, since no 2D Readers may be installed and + * getLuminances() may never be called. + * + * @param int $y The row to fetch, which must be in [0,getHeight()) + * + * @return array An array containing the luminance data. + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function getRow(int $y):array; + + /** + * Creates a LuminanceSource instance from the given file + */ + public static function fromFile(string $path):self; + + /** + * Creates a LuminanceSource instance from the given data blob + */ + public static function fromBlob(string $blob):self; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php b/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php new file mode 100644 index 000000000..8441a7f53 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php @@ -0,0 +1,329 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; +use chillerlan\QRCode\Data\QRMatrix; +use Closure; +use function abs, array_column, array_search, intdiv, min; + +/** + * ISO/IEC 18004:2000 Section 8.8.1 + * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results + * + * @see http://www.thonky.com/qr-code-tutorial/data-masking + * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java + */ +final class MaskPattern{ + + /** + * @see \chillerlan\QRCode\QROptionsTrait::$maskPattern + * + * @var int + */ + public const AUTO = -1; + + public const PATTERN_000 = 0b000; + public const PATTERN_001 = 0b001; + public const PATTERN_010 = 0b010; + public const PATTERN_011 = 0b011; + public const PATTERN_100 = 0b100; + public const PATTERN_101 = 0b101; + public const PATTERN_110 = 0b110; + public const PATTERN_111 = 0b111; + + /** + * @var int[] + */ + public const PATTERNS = [ + self::PATTERN_000, + self::PATTERN_001, + self::PATTERN_010, + self::PATTERN_011, + self::PATTERN_100, + self::PATTERN_101, + self::PATTERN_110, + self::PATTERN_111, + ]; + + /* + * Penalty scores + * + * ISO/IEC 18004:2000 Section 8.8.1 - Table 24 + */ + private const PENALTY_N1 = 3; + private const PENALTY_N2 = 3; + private const PENALTY_N3 = 40; + private const PENALTY_N4 = 10; + + /** + * The current mask pattern value (0-7) + */ + private int $maskPattern; + + /** + * MaskPattern constructor. + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $maskPattern){ + + if((0b111 & $maskPattern) !== $maskPattern){ + throw new QRCodeException('invalid mask pattern'); + } + + $this->maskPattern = $maskPattern; + } + + /** + * Returns the current mask pattern + */ + public function getPattern():int{ + return $this->maskPattern; + } + + /** + * Returns a closure that applies the mask for the chosen mask pattern. + * + * Note that the diagram in section 6.8.1 is misleading since it indicates that $i is column position + * and $j is row position. In fact, as the text says, $i is row position and $j is column position. + * + * @see https://www.thonky.com/qr-code-tutorial/mask-patterns + * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java#L32-L117 + */ + public function getMask():Closure{ + // $x = column (width), $y = row (height) + return [ + self::PATTERN_000 => fn(int $x, int $y):bool => (($x + $y) % 2) === 0, + self::PATTERN_001 => fn(int $x, int $y):bool => ($y % 2) === 0, + self::PATTERN_010 => fn(int $x, int $y):bool => ($x % 3) === 0, + self::PATTERN_011 => fn(int $x, int $y):bool => (($x + $y) % 3) === 0, + self::PATTERN_100 => fn(int $x, int $y):bool => ((intdiv($y, 2) + intdiv($x, 3)) % 2) === 0, + self::PATTERN_101 => fn(int $x, int $y):bool => (($x * $y) % 6) === 0, + self::PATTERN_110 => fn(int $x, int $y):bool => (($x * $y) % 6) < 3, + self::PATTERN_111 => fn(int $x, int $y):bool => (($x + $y + (($x * $y) % 3)) % 2) === 0, + ][$this->maskPattern]; + } + + /** + * Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result + */ + public static function getBestPattern(QRMatrix $QRMatrix):self{ + $penalties = []; + $size = $QRMatrix->getSize(); + + foreach(self::PATTERNS as $pattern){ + $mp = new self($pattern); + $matrix = (clone $QRMatrix)->setFormatInfo($mp)->mask($mp)->getMatrix(true); + $penalty = 0; + + for($level = 1; $level <= 4; $level++){ + $penalty += self::{'testRule'.$level}($matrix, $size, $size); + } + + $penalties[$pattern] = (int)$penalty; + } + + return new self(array_search(min($penalties), $penalties, true)); + } + + /** + * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + * give penalty to them. Example: 00000 or 11111. + */ + public static function testRule1(array $matrix, int $height, int $width):int{ + $penalty = 0; + + // horizontal + foreach($matrix as $row){ + $penalty += self::applyRule1($row); + } + + // vertical + for($x = 0; $x < $width; $x++){ + $penalty += self::applyRule1(array_column($matrix, $x)); + } + + return $penalty; + } + + /** + * + */ + private static function applyRule1(array $rc):int{ + $penalty = 0; + $numSameBitCells = 0; + $prevBit = null; + + foreach($rc as $val){ + + if($val === $prevBit){ + $numSameBitCells++; + } + else{ + + if($numSameBitCells >= 5){ + $penalty += (self::PENALTY_N1 + $numSameBitCells - 5); + } + + $numSameBitCells = 1; // Include the cell itself. + $prevBit = $val; + } + } + + if($numSameBitCells >= 5){ + $penalty += (self::PENALTY_N1 + $numSameBitCells - 5); + } + + return $penalty; + } + + /** + * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a + * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. + */ + public static function testRule2(array $matrix, int $height, int $width):int{ + $penalty = 0; + + foreach($matrix as $y => $row){ + + if($y > ($height - 2)){ + break; + } + + foreach($row as $x => $val){ + + if($x > ($width - 2)){ + break; + } + + if( + $val === $row[($x + 1)] + && $val === $matrix[($y + 1)][$x] + && $val === $matrix[($y + 1)][($x + 1)] + ){ + $penalty++; + } + } + } + + return (self::PENALTY_N2 * $penalty); + } + + /** + * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 + * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we + * find patterns like 000010111010000, we give penalty once. + */ + public static function testRule3(array $matrix, int $height, int $width):int{ + $penalties = 0; + + foreach($matrix as $y => $row){ + foreach($row as $x => $val){ + + if( + ($x + 6) < $width + && $val + && !$row[($x + 1)] + && $row[($x + 2)] + && $row[($x + 3)] + && $row[($x + 4)] + && !$row[($x + 5)] + && $row[($x + 6)] + && ( + self::isWhiteHorizontal($row, $width, ($x - 4), $x) + || self::isWhiteHorizontal($row, $width, ($x + 7), ($x + 11)) + ) + ){ + $penalties++; + } + + if( + ($y + 6) < $height + && $val + && !$matrix[($y + 1)][$x] + && $matrix[($y + 2)][$x] + && $matrix[($y + 3)][$x] + && $matrix[($y + 4)][$x] + && !$matrix[($y + 5)][$x] + && $matrix[($y + 6)][$x] + && ( + self::isWhiteVertical($matrix, $height, $x, ($y - 4), $y) + || self::isWhiteVertical($matrix, $height, $x, ($y + 7), ($y + 11)) + ) + ){ + $penalties++; + } + + } + } + + return ($penalties * self::PENALTY_N3); + } + + /** + * + */ + private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{ + + if($from < 0 || $width < $to){ + return false; + } + + for($x = $from; $x < $to; $x++){ + if($row[$x]){ + return false; + } + } + + return true; + } + + /** + * + */ + private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{ + + if($from < 0 || $height < $to){ + return false; + } + + for($y = $from; $y < $to; $y++){ + if($matrix[$y][$x] === true){ + return false; + } + } + + return true; + } + + /** + * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. + */ + public static function testRule4(array $matrix, int $height, int $width):int{ + $darkCells = 0; + $totalCells = ($height * $width); + + foreach($matrix as $row){ + foreach($row as $val){ + if($val === true){ + $darkCells++; + } + } + } + + return (intdiv((abs($darkCells * 2 - $totalCells) * 10), $totalCells) * self::PENALTY_N4); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php b/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php new file mode 100644 index 000000000..523d37919 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/Mode.php @@ -0,0 +1,96 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\Data\{AlphaNum, Byte, Hanzi, Kanji, Number}; +use chillerlan\QRCode\QRCodeException; + +/** + * Data mode information - ISO 18004:2006, 6.4.1, Tables 2 and 3 + */ +final class Mode{ + + // ISO/IEC 18004:2000 Table 2 + + /** @var int */ + public const TERMINATOR = 0b0000; + /** @var int */ + public const NUMBER = 0b0001; + /** @var int */ + public const ALPHANUM = 0b0010; + /** @var int */ + public const BYTE = 0b0100; + /** @var int */ + public const KANJI = 0b1000; + /** @var int */ + public const HANZI = 0b1101; + /** @var int */ + public const STRCTURED_APPEND = 0b0011; + /** @var int */ + public const FNC1_FIRST = 0b0101; + /** @var int */ + public const FNC1_SECOND = 0b1001; + /** @var int */ + public const ECI = 0b0111; + + /** + * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator + */ + public const LENGTH_BITS = [ + self::NUMBER => [10, 12, 14], + self::ALPHANUM => [ 9, 11, 13], + self::BYTE => [ 8, 16, 16], + self::KANJI => [ 8, 10, 12], + self::HANZI => [ 8, 10, 12], + self::ECI => [ 0, 0, 0], + ]; + + /** + * Map of data mode => interface (detection order) + * + * @var string[] + */ + public const INTERFACES = [ + self::NUMBER => Number::class, + self::ALPHANUM => AlphaNum::class, + self::KANJI => Kanji::class, + self::HANZI => Hanzi::class, + self::BYTE => Byte::class, + ]; + + /** + * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function getLengthBitsForVersion(int $mode, int $version):int{ + + if(!isset(self::LENGTH_BITS[$mode])){ + throw new QRCodeException('invalid mode given'); + } + + $minVersion = 0; + + foreach([9, 26, 40] as $key => $breakpoint){ + + if($version > $minVersion && $version <= $breakpoint){ + return self::LENGTH_BITS[$mode][$key]; + } + + $minVersion = $breakpoint; + } + + throw new QRCodeException(sprintf('invalid version number: %d', $version)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Common/Version.php b/src/vendor/chillerlan/php-qrcode/src/Common/Version.php new file mode 100644 index 000000000..fe7240f8a --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Common/Version.php @@ -0,0 +1,287 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Common; + +use chillerlan\QRCode\QRCodeException; + +/** + * Version related tables and methods + */ +final class Version{ + + /** + * Enable version auto detection + * + * @see \chillerlan\QRCode\QROptionsTrait::$version + * + * @var int + */ + public const AUTO = -1; + + /** + * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns + * + * version -> pattern + * + * @var int[][] + */ + private const ALIGNMENT_PATTERN = [ + 1 => [], + 2 => [6, 18], + 3 => [6, 22], + 4 => [6, 26], + 5 => [6, 30], + 6 => [6, 34], + 7 => [6, 22, 38], + 8 => [6, 24, 42], + 9 => [6, 26, 46], + 10 => [6, 28, 50], + 11 => [6, 30, 54], + 12 => [6, 32, 58], + 13 => [6, 34, 62], + 14 => [6, 26, 46, 66], + 15 => [6, 26, 48, 70], + 16 => [6, 26, 50, 74], + 17 => [6, 30, 54, 78], + 18 => [6, 30, 56, 82], + 19 => [6, 30, 58, 86], + 20 => [6, 34, 62, 90], + 21 => [6, 28, 50, 72, 94], + 22 => [6, 26, 50, 74, 98], + 23 => [6, 30, 54, 78, 102], + 24 => [6, 28, 54, 80, 106], + 25 => [6, 32, 58, 84, 110], + 26 => [6, 30, 58, 86, 114], + 27 => [6, 34, 62, 90, 118], + 28 => [6, 26, 50, 74, 98, 122], + 29 => [6, 30, 54, 78, 102, 126], + 30 => [6, 26, 52, 78, 104, 130], + 31 => [6, 30, 56, 82, 108, 134], + 32 => [6, 34, 60, 86, 112, 138], + 33 => [6, 30, 58, 86, 114, 142], + 34 => [6, 34, 62, 90, 118, 146], + 35 => [6, 30, 54, 78, 102, 126, 150], + 36 => [6, 24, 50, 76, 102, 128, 154], + 37 => [6, 28, 54, 80, 106, 132, 158], + 38 => [6, 32, 58, 84, 110, 136, 162], + 39 => [6, 26, 54, 82, 110, 138, 166], + 40 => [6, 30, 58, 86, 114, 142, 170], + ]; + + /** + * ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version + * + * no version pattern for QR Codes < 7 + * + * @var int[] + */ + private const VERSION_PATTERN = [ + 7 => 0b000111110010010100, + 8 => 0b001000010110111100, + 9 => 0b001001101010011001, + 10 => 0b001010010011010011, + 11 => 0b001011101111110110, + 12 => 0b001100011101100010, + 13 => 0b001101100001000111, + 14 => 0b001110011000001101, + 15 => 0b001111100100101000, + 16 => 0b010000101101111000, + 17 => 0b010001010001011101, + 18 => 0b010010101000010111, + 19 => 0b010011010100110010, + 20 => 0b010100100110100110, + 21 => 0b010101011010000011, + 22 => 0b010110100011001001, + 23 => 0b010111011111101100, + 24 => 0b011000111011000100, + 25 => 0b011001000111100001, + 26 => 0b011010111110101011, + 27 => 0b011011000010001110, + 28 => 0b011100110000011010, + 29 => 0b011101001100111111, + 30 => 0b011110110101110101, + 31 => 0b011111001001010000, + 32 => 0b100000100111010101, + 33 => 0b100001011011110000, + 34 => 0b100010100010111010, + 35 => 0b100011011110011111, + 36 => 0b100100101100001011, + 37 => 0b100101010000101110, + 38 => 0b100110101001100100, + 39 => 0b100111010101000001, + 40 => 0b101000110001101001, + ]; + + /** + * ISO/IEC 18004:2000 Tables 13-22 - Error correction characteristics + * + * @see http://www.thonky.com/qr-code-tutorial/error-correction-table + */ + private const RSBLOCKS = [ + 1 => [[ 7, [[ 1, 19], [ 0, 0]]], [10, [[ 1, 16], [ 0, 0]]], [13, [[ 1, 13], [ 0, 0]]], [17, [[ 1, 9], [ 0, 0]]]], + 2 => [[10, [[ 1, 34], [ 0, 0]]], [16, [[ 1, 28], [ 0, 0]]], [22, [[ 1, 22], [ 0, 0]]], [28, [[ 1, 16], [ 0, 0]]]], + 3 => [[15, [[ 1, 55], [ 0, 0]]], [26, [[ 1, 44], [ 0, 0]]], [18, [[ 2, 17], [ 0, 0]]], [22, [[ 2, 13], [ 0, 0]]]], + 4 => [[20, [[ 1, 80], [ 0, 0]]], [18, [[ 2, 32], [ 0, 0]]], [26, [[ 2, 24], [ 0, 0]]], [16, [[ 4, 9], [ 0, 0]]]], + 5 => [[26, [[ 1, 108], [ 0, 0]]], [24, [[ 2, 43], [ 0, 0]]], [18, [[ 2, 15], [ 2, 16]]], [22, [[ 2, 11], [ 2, 12]]]], + 6 => [[18, [[ 2, 68], [ 0, 0]]], [16, [[ 4, 27], [ 0, 0]]], [24, [[ 4, 19], [ 0, 0]]], [28, [[ 4, 15], [ 0, 0]]]], + 7 => [[20, [[ 2, 78], [ 0, 0]]], [18, [[ 4, 31], [ 0, 0]]], [18, [[ 2, 14], [ 4, 15]]], [26, [[ 4, 13], [ 1, 14]]]], + 8 => [[24, [[ 2, 97], [ 0, 0]]], [22, [[ 2, 38], [ 2, 39]]], [22, [[ 4, 18], [ 2, 19]]], [26, [[ 4, 14], [ 2, 15]]]], + 9 => [[30, [[ 2, 116], [ 0, 0]]], [22, [[ 3, 36], [ 2, 37]]], [20, [[ 4, 16], [ 4, 17]]], [24, [[ 4, 12], [ 4, 13]]]], + 10 => [[18, [[ 2, 68], [ 2, 69]]], [26, [[ 4, 43], [ 1, 44]]], [24, [[ 6, 19], [ 2, 20]]], [28, [[ 6, 15], [ 2, 16]]]], + 11 => [[20, [[ 4, 81], [ 0, 0]]], [30, [[ 1, 50], [ 4, 51]]], [28, [[ 4, 22], [ 4, 23]]], [24, [[ 3, 12], [ 8, 13]]]], + 12 => [[24, [[ 2, 92], [ 2, 93]]], [22, [[ 6, 36], [ 2, 37]]], [26, [[ 4, 20], [ 6, 21]]], [28, [[ 7, 14], [ 4, 15]]]], + 13 => [[26, [[ 4, 107], [ 0, 0]]], [22, [[ 8, 37], [ 1, 38]]], [24, [[ 8, 20], [ 4, 21]]], [22, [[12, 11], [ 4, 12]]]], + 14 => [[30, [[ 3, 115], [ 1, 116]]], [24, [[ 4, 40], [ 5, 41]]], [20, [[11, 16], [ 5, 17]]], [24, [[11, 12], [ 5, 13]]]], + 15 => [[22, [[ 5, 87], [ 1, 88]]], [24, [[ 5, 41], [ 5, 42]]], [30, [[ 5, 24], [ 7, 25]]], [24, [[11, 12], [ 7, 13]]]], + 16 => [[24, [[ 5, 98], [ 1, 99]]], [28, [[ 7, 45], [ 3, 46]]], [24, [[15, 19], [ 2, 20]]], [30, [[ 3, 15], [13, 16]]]], + 17 => [[28, [[ 1, 107], [ 5, 108]]], [28, [[10, 46], [ 1, 47]]], [28, [[ 1, 22], [15, 23]]], [28, [[ 2, 14], [17, 15]]]], + 18 => [[30, [[ 5, 120], [ 1, 121]]], [26, [[ 9, 43], [ 4, 44]]], [28, [[17, 22], [ 1, 23]]], [28, [[ 2, 14], [19, 15]]]], + 19 => [[28, [[ 3, 113], [ 4, 114]]], [26, [[ 3, 44], [11, 45]]], [26, [[17, 21], [ 4, 22]]], [26, [[ 9, 13], [16, 14]]]], + 20 => [[28, [[ 3, 107], [ 5, 108]]], [26, [[ 3, 41], [13, 42]]], [30, [[15, 24], [ 5, 25]]], [28, [[15, 15], [10, 16]]]], + 21 => [[28, [[ 4, 116], [ 4, 117]]], [26, [[17, 42], [ 0, 0]]], [28, [[17, 22], [ 6, 23]]], [30, [[19, 16], [ 6, 17]]]], + 22 => [[28, [[ 2, 111], [ 7, 112]]], [28, [[17, 46], [ 0, 0]]], [30, [[ 7, 24], [16, 25]]], [24, [[34, 13], [ 0, 0]]]], + 23 => [[30, [[ 4, 121], [ 5, 122]]], [28, [[ 4, 47], [14, 48]]], [30, [[11, 24], [14, 25]]], [30, [[16, 15], [14, 16]]]], + 24 => [[30, [[ 6, 117], [ 4, 118]]], [28, [[ 6, 45], [14, 46]]], [30, [[11, 24], [16, 25]]], [30, [[30, 16], [ 2, 17]]]], + 25 => [[26, [[ 8, 106], [ 4, 107]]], [28, [[ 8, 47], [13, 48]]], [30, [[ 7, 24], [22, 25]]], [30, [[22, 15], [13, 16]]]], + 26 => [[28, [[10, 114], [ 2, 115]]], [28, [[19, 46], [ 4, 47]]], [28, [[28, 22], [ 6, 23]]], [30, [[33, 16], [ 4, 17]]]], + 27 => [[30, [[ 8, 122], [ 4, 123]]], [28, [[22, 45], [ 3, 46]]], [30, [[ 8, 23], [26, 24]]], [30, [[12, 15], [28, 16]]]], + 28 => [[30, [[ 3, 117], [10, 118]]], [28, [[ 3, 45], [23, 46]]], [30, [[ 4, 24], [31, 25]]], [30, [[11, 15], [31, 16]]]], + 29 => [[30, [[ 7, 116], [ 7, 117]]], [28, [[21, 45], [ 7, 46]]], [30, [[ 1, 23], [37, 24]]], [30, [[19, 15], [26, 16]]]], + 30 => [[30, [[ 5, 115], [10, 116]]], [28, [[19, 47], [10, 48]]], [30, [[15, 24], [25, 25]]], [30, [[23, 15], [25, 16]]]], + 31 => [[30, [[13, 115], [ 3, 116]]], [28, [[ 2, 46], [29, 47]]], [30, [[42, 24], [ 1, 25]]], [30, [[23, 15], [28, 16]]]], + 32 => [[30, [[17, 115], [ 0, 0]]], [28, [[10, 46], [23, 47]]], [30, [[10, 24], [35, 25]]], [30, [[19, 15], [35, 16]]]], + 33 => [[30, [[17, 115], [ 1, 116]]], [28, [[14, 46], [21, 47]]], [30, [[29, 24], [19, 25]]], [30, [[11, 15], [46, 16]]]], + 34 => [[30, [[13, 115], [ 6, 116]]], [28, [[14, 46], [23, 47]]], [30, [[44, 24], [ 7, 25]]], [30, [[59, 16], [ 1, 17]]]], + 35 => [[30, [[12, 121], [ 7, 122]]], [28, [[12, 47], [26, 48]]], [30, [[39, 24], [14, 25]]], [30, [[22, 15], [41, 16]]]], + 36 => [[30, [[ 6, 121], [14, 122]]], [28, [[ 6, 47], [34, 48]]], [30, [[46, 24], [10, 25]]], [30, [[ 2, 15], [64, 16]]]], + 37 => [[30, [[17, 122], [ 4, 123]]], [28, [[29, 46], [14, 47]]], [30, [[49, 24], [10, 25]]], [30, [[24, 15], [46, 16]]]], + 38 => [[30, [[ 4, 122], [18, 123]]], [28, [[13, 46], [32, 47]]], [30, [[48, 24], [14, 25]]], [30, [[42, 15], [32, 16]]]], + 39 => [[30, [[20, 117], [ 4, 118]]], [28, [[40, 47], [ 7, 48]]], [30, [[43, 24], [22, 25]]], [30, [[10, 15], [67, 16]]]], + 40 => [[30, [[19, 118], [ 6, 119]]], [28, [[18, 47], [31, 48]]], [30, [[34, 24], [34, 25]]], [30, [[20, 15], [61, 16]]]], + ]; + + /** + * ISO/IEC 18004:2000 Table 1 - Data capacity of all versions of QR Code + */ + private const TOTAL_CODEWORDS = [ + 1 => 26, + 2 => 44, + 3 => 70, + 4 => 100, + 5 => 134, + 6 => 172, + 7 => 196, + 8 => 242, + 9 => 292, + 10 => 346, + 11 => 404, + 12 => 466, + 13 => 532, + 14 => 581, + 15 => 655, + 16 => 733, + 17 => 815, + 18 => 901, + 19 => 991, + 20 => 1085, + 21 => 1156, + 22 => 1258, + 23 => 1364, + 24 => 1474, + 25 => 1588, + 26 => 1706, + 27 => 1828, + 28 => 1921, + 29 => 2051, + 30 => 2185, + 31 => 2323, + 32 => 2465, + 33 => 2611, + 34 => 2761, + 35 => 2876, + 36 => 3034, + 37 => 3196, + 38 => 3362, + 39 => 3532, + 40 => 3706, + ]; + + /** + * QR Code version number + */ + private int $version; + + /** + * Version constructor. + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function __construct(int $version){ + + if($version < 1 || $version > 40){ + throw new QRCodeException('invalid version given'); + } + + $this->version = $version; + } + + /** + * returns the current version number as string + */ + public function __toString():string{ + return (string)$this->version; + } + + /** + * returns the current version number + */ + public function getVersionNumber():int{ + return $this->version; + } + + /** + * the matrix size for the given version + */ + public function getDimension():int{ + return (($this->version * 4) + 17); + } + + /** + * the version pattern for the given version + */ + public function getVersionPattern():?int{ + return (self::VERSION_PATTERN[$this->version] ?? null); + } + + /** + * the alignment patterns for the current version + * + * @return int[] + */ + public function getAlignmentPattern():array{ + return self::ALIGNMENT_PATTERN[$this->version]; + } + + /** + * returns ECC block information for the given $version and $eccLevel + */ + public function getRSBlocks(EccLevel $eccLevel):array{ + return self::RSBLOCKS[$this->version][$eccLevel->getOrdinal()]; + } + + /** + * returns the maximum codewords for the current version + */ + public function getTotalCodewords():int{ + return self::TOTAL_CODEWORDS[$this->version]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php b/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php new file mode 100644 index 000000000..77242d728 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php @@ -0,0 +1,137 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function array_flip, ceil, intdiv, str_split; + +/** + * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : + * + * ISO/IEC 18004:2000 Section 8.3.3 + * ISO/IEC 18004:2000 Section 8.4.3 + */ +final class AlphaNum extends QRDataModeAbstract{ + + /** + * ISO/IEC 18004:2000 Table 5 + * + * @var int[] + */ + private const CHAR_TO_ORD = [ + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, + '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, + 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, + 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, + 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39, + '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, + ]; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ALPHANUM; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (11 / 2)); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + + if($string === ''){ + return false; + } + + foreach(str_split($string) as $chr){ + if(!isset(self::CHAR_TO_ORD[$chr])){ + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + // encode 2 characters in 11 bits + for($i = 0; ($i + 1) < $len; $i += 2){ + $bitBuffer->put((self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]), 11); + } + + // encode a remaining character in 6 bits + if($i < $len){ + $bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]], 6); + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $charmap = array_flip(self::CHAR_TO_ORD); + + // @todo + $toAlphaNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read two characters at a time + while($length > 1){ + + if($bitBuffer->available() < 11){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $nextTwoCharsBits = $bitBuffer->read(11); + $result .= $toAlphaNumericChar(intdiv($nextTwoCharsBits, 45)); + $result .= $toAlphaNumericChar($nextTwoCharsBits % 45); + $length -= 2; + } + + if($length === 1){ + // special case: one character left + if($bitBuffer->available() < 6){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $result .= $toAlphaNumericChar($bitBuffer->read(6)); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php b/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php new file mode 100644 index 000000000..10ab85262 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Byte.php @@ -0,0 +1,85 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function chr, ord; + +/** + * 8-bit Byte mode, ISO-8859-1 or UTF-8 + * + * ISO/IEC 18004:2000 Section 8.3.4 + * ISO/IEC 18004:2000 Section 8.4.4 + */ +final class Byte extends QRDataModeAbstract{ + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::BYTE; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 8); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + return $string !== ''; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + $i = 0; + + while($i < $len){ + $bitBuffer->put(ord($this->data[$i]), 8); + $i++; + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < (8 * $length)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $readBytes = ''; + + for($i = 0; $i < $length; $i++){ + $readBytes .= chr($bitBuffer->read(8)); + } + + return $readBytes; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php b/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php new file mode 100644 index 000000000..3029e8373 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/ECI.php @@ -0,0 +1,155 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode}; +use function mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, sprintf; + +/** + * Adds an ECI Designator + * + * ISO/IEC 18004:2000 8.4.1.1 + * + * Please note that you have to take care for the correct data encoding when adding with QRCode::add*Segment() + */ +final class ECI extends QRDataModeAbstract{ + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ECI; + + /** + * The current ECI encoding id + */ + private int $encoding; + + /** + * @inheritDoc + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(int $encoding){ + + if($encoding < 0 || $encoding > 999999){ + throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding)); + } + + $this->encoding = $encoding; + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + + if($this->encoding < 128){ + return 8; + } + + if($this->encoding < 16384){ + return 16; + } + + return 24; + } + + /** + * Writes an ECI designator to the bitbuffer + * + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $bitBuffer->put(self::DATAMODE, 4); + + if($this->encoding < 128){ + $bitBuffer->put($this->encoding, 8); + } + elseif($this->encoding < 16384){ + $bitBuffer->put(($this->encoding | 0x8000), 16); + } + elseif($this->encoding < 1000000){ + $bitBuffer->put(($this->encoding | 0xC00000), 24); + } + else{ + throw new QRCodeDataException('invalid ECI ID'); + } + + return $this; + } + + /** + * Reads and parses the value of an ECI designator + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function parseValue(BitBuffer $bitBuffer):ECICharset{ + $firstByte = $bitBuffer->read(8); + + // just one byte + if(($firstByte & 0b10000000) === 0){ + $id = ($firstByte & 0b01111111); + } + // two bytes + elseif(($firstByte & 0b11000000) === 0b10000000){ + $id = ((($firstByte & 0b00111111) << 8) | $bitBuffer->read(8)); + } + // three bytes + elseif(($firstByte & 0b11100000) === 0b11000000){ + $id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16)); + } + else{ + throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte)); // @codeCoverageIgnore + } + + return new ECICharset($id); + } + + /** + * @codeCoverageIgnore Unused, but required as per interface + */ + public static function validateString(string $string):bool{ + return true; + } + + /** + * Reads and decodes the ECI designator including the following byte sequence + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $eciCharset = self::parseValue($bitBuffer); + $nextMode = $bitBuffer->read(4); + + if($nextMode !== Mode::BYTE){ + throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $nextMode)); + } + + $data = Byte::decodeSegment($bitBuffer, $versionNumber); + $encoding = $eciCharset->getName(); + + if($encoding === null){ + // The spec isn't clear on this mode; see + // section 6.4.5: t does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + $encoding = mb_detect_encoding($data, ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'], true); + + if($encoding === false){ + throw new QRCodeDataException('could not determine encoding in ECI mode'); // @codeCoverageIgnore + } + } + + return mb_convert_encoding($data, mb_internal_encoding(), $encoding); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php b/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php new file mode 100644 index 000000000..4476ad9d2 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php @@ -0,0 +1,205 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; + +/** + * Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set + * + * Please note that this is not part of the QR Code specification and may not be supported by all readers (ZXing-based ones do). + * + * @see https://en.wikipedia.org/wiki/GB_2312 + * @see http://www.herongyang.com/GB2312/Introduction-of-GB2312.html + * @see https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding + * @see https://gist.github.com/codemasher/91da33c44bfb48a81a6c1426bb8e4338 + * @see https://github.com/zxing/zxing/blob/dfb06fa33b17a9e68321be151c22846c7b78048f/core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java#L172-L209 + * @see https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000 + */ +final class Hanzi extends QRDataModeAbstract{ + + /** + * possible values: GB2312, GB18030 + * + * @var string + */ + public const ENCODING = 'GB18030'; + + /** + * @todo: other subsets??? + * + * @var int + */ + public const GB2312_SUBSET = 0b0001; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::HANZI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException('mb_convert_encoding error'); + } + + return $string; + } + + /** + * checks if a string qualifies as Hanzi/GB2312 + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused ranges + if($byte1 < 0xa1 || ($byte1 > 0xa9 && $byte1 < 0xb0) || $byte1 > 0xf7){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0xa1 || $byte2 > 0xfe){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this::GB2312_SUBSET, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); + + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); + + if($c >= 0xa1a1 && $c <= 0xaafe){ + $c -= 0x0a1a1; + } + elseif($c >= 0xb0a1 && $c <= 0xfafe){ + $c -= 0x0a6a1; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); + } + + $bitBuffer->put((((($c >> 8) & 0xff) * 0x060) + ($c & 0xff)), 13); + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * See specification GBT 18284-2000 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + + // Hanzi mode contains a subset indicator right after mode indicator + if($bitBuffer->read(4) !== self::GB2312_SUBSET){ + throw new QRCodeDataException('ecpected subset indicator for Hanzi mode'); + } + + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as GB2312 afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x060) << 8) | ($twoBytes % 0x060)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x00a00) // 0x003BF + ? 0x0a1a1 // In the 0xA1A1 to 0xAAFE range + : 0x0a6a1; // In the 0xB0A1 to 0xFAFE range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php b/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php new file mode 100644 index 000000000..e42f96dc3 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Kanji.php @@ -0,0 +1,191 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; + +/** + * Kanji mode: 13-bit double-byte characters from the Shift-JIS character set + * + * ISO/IEC 18004:2000 Section 8.3.5 + * ISO/IEC 18004:2000 Section 8.4.5 + * + * @see https://en.wikipedia.org/wiki/Shift_JIS#As_defined_in_JIS_X_0208:1997 + * @see http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml + * @see https://gist.github.com/codemasher/d07d3e6e9346c08e7a41b8b978784952 + */ +final class Kanji extends QRDataModeAbstract{ + + /** + * possible values: SJIS, SJIS-2004 + * + * SJIS-2004 may produce errors in PHP < 8 + * + * @var string + */ + public const ENCODING = 'SJIS'; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::KANJI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException(sprintf('invalid encoding: %s', $detected)); + } + + return $string; + } + + /** + * checks if a string qualifies as SJIS Kanji + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused and vendor ranges + if($byte1 < 0x81 || ($byte1 > 0x84 && $byte1 < 0x88) || ($byte1 > 0x9f && $byte1 < 0xe0) || $byte1 > 0xea){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0x40 || $byte2 === 0x7f || $byte2 > 0xfc){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); + + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); + + if($c >= 0x8140 && $c <= 0x9ffc){ + $c -= 0x8140; + } + elseif($c >= 0xe040 && $c <= 0xebbf){ + $c -= 0xc140; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); + } + + $bitBuffer->put((((($c >> 8) & 0xff) * 0xc0) + ($c & 0xff)), 13); + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as SJIS afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x0c0) << 8) | ($twoBytes % 0x0c0)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x01f00) + ? 0x08140 // In the 0x8140 to 0x9FFC range + : 0x0c140; // In the 0xE040 to 0xEBBF range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/Number.php b/src/vendor/chillerlan/php-qrcode/src/Data/Number.php new file mode 100644 index 000000000..285be3736 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/Number.php @@ -0,0 +1,182 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function array_flip, ceil, intdiv, str_split, substr, unpack; + +/** + * Numeric mode: decimal digits 0 to 9 + * + * ISO/IEC 18004:2000 Section 8.3.2 + * ISO/IEC 18004:2000 Section 8.4.2 + */ +final class Number extends QRDataModeAbstract{ + + /** + * @var int[] + */ + private const NUMBER_TO_ORD = [ + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + ]; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::NUMBER; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (10 / 3)); + } + + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + + if($string === ''){ + return false; + } + + foreach(str_split($string) as $chr){ + if(!isset(self::NUMBER_TO_ORD[$chr])){ + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + $i = 0; + + // encode numeric triplets in 10 bits + while(($i + 2) < $len){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10); + $i += 3; + } + + if($i < $len){ + + // encode 2 remaining numbers in 7 bits + if(($len - $i) === 2){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7); + } + // encode one remaining number in 4 bits + elseif(($len - $i) === 1){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4); + } + + } + + return $this; + } + + /** + * get the code for the given numeric string + */ + private function parseInt(string $string):int{ + $num = 0; + + foreach(unpack('C*', $string) as $chr){ + $num = ($num * 10 + $chr - 48); + } + + return $num; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $charmap = array_flip(self::NUMBER_TO_ORD); + + // @todo + $toNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read three digits at a time + while($length >= 3){ + // Each 10 bits encodes three digits + if($bitBuffer->available() < 10){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $threeDigitsBits = $bitBuffer->read(10); + + if($threeDigitsBits >= 1000){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar(intdiv($threeDigitsBits, 100)); + $result .= $toNumericChar(intdiv($threeDigitsBits, 10) % 10); + $result .= $toNumericChar($threeDigitsBits % 10); + + $length -= 3; + } + + if($length === 2){ + // Two digits left over to read, encoded in 7 bits + if($bitBuffer->available() < 7){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $twoDigitsBits = $bitBuffer->read(7); + + if($twoDigitsBits >= 100){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar(intdiv($twoDigitsBits, 10)); + $result .= $toNumericChar($twoDigitsBits % 10); + } + elseif($length === 1){ + // One digit left over to read + if($bitBuffer->available() < 4){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $digitBits = $bitBuffer->read(4); + + if($digitBits >= 10){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar($digitBits); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php new file mode 100644 index 000000000..04ffbd7ed --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDataException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php new file mode 100644 index 000000000..4b2dcf778 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRData.php @@ -0,0 +1,263 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, Mode, Version}; +use chillerlan\Settings\SettingsContainerInterface; +use function sprintf; + +/** + * Processes the binary data and maps it on a QRMatrix which is then being returned + */ +final class QRData{ + + /** + * the options instance + * + * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions + */ + private SettingsContainerInterface $options; + + /** + * a BitBuffer instance + */ + private BitBuffer $bitBuffer; + + /** + * an EccLevel instance + */ + private EccLevel $eccLevel; + + /** + * current QR Code version + */ + private Version $version; + + /** + * @var \chillerlan\QRCode\Data\QRDataModeInterface[] + */ + private array $dataSegments = []; + + /** + * Max bits for the current ECC mode + * + * @var int[] + */ + private array $maxBitsForEcc; + + /** + * QRData constructor. + */ + public function __construct(SettingsContainerInterface $options, array $dataSegments = []){ + $this->options = $options; + $this->bitBuffer = new BitBuffer; + $this->eccLevel = new EccLevel($this->options->eccLevel); + $this->maxBitsForEcc = $this->eccLevel->getMaxBits(); + + $this->setData($dataSegments); + } + + /** + * Sets the data string (internally called by the constructor) + * + * Subsequent calls will overwrite the current state - use the QRCode::add*Segement() method instead + * + * @param \chillerlan\QRCode\Data\QRDataModeInterface[] $dataSegments + */ + public function setData(array $dataSegments):self{ + $this->dataSegments = $dataSegments; + $this->version = $this->getMinimumVersion(); + + $this->bitBuffer->clear(); + $this->writeBitBuffer(); + + return $this; + } + + /** + * Returns the current BitBuffer instance + * + * @codeCoverageIgnore + */ + public function getBitBuffer():BitBuffer{ + return $this->bitBuffer; + } + + /** + * Sets a BitBuffer object + * + * This can be used instead of setData(), however, the version auto-detection is not available in this case. + * The version needs to match the length bits range for the data mode the data has been encoded with, + * additionally the bit array needs to contain enough pad bits. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setBitBuffer(BitBuffer $bitBuffer):self{ + + if($this->options->version === Version::AUTO){ + throw new QRCodeDataException('version auto detection is not available'); + } + + if($bitBuffer->getLength() === 0){ + throw new QRCodeDataException('the given BitBuffer is empty'); + } + + $this->dataSegments = []; + $this->bitBuffer = $bitBuffer; + $this->version = new Version($this->options->version); + + return $this; + } + + /** + * returns a fresh matrix object with the data written and masked with the given $maskPattern + */ + public function writeMatrix():QRMatrix{ + return (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->writeCodewords($this->bitBuffer) + ; + } + + /** + * estimates the total length of the several mode segments in order to guess the minimum version + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function estimateTotalBitLength():int{ + $length = 0; + + foreach($this->dataSegments as $segment){ + // data length of the current segment + $length += $segment->getLengthInBits(); + // +4 bits for the mode descriptor + $length += 4; + // Hanzi mode sets an additional 4 bit long subset identifier + if($segment instanceof Hanzi){ + $length += 4; + } + } + + $provisionalVersion = null; + + foreach($this->maxBitsForEcc as $version => $maxBits){ + + if($length <= $maxBits){ + $provisionalVersion = $version; + } + + } + + if($provisionalVersion !== null){ + + // add character count indicator bits for the provisional version + foreach($this->dataSegments as $segment){ + $length += Mode::getLengthBitsForVersion($segment::DATAMODE, $provisionalVersion); + } + + // it seems that in some cases the estimated total length is not 100% accurate, + // so we substract 4 bits from the total when not in mixed mode + if(count($this->dataSegments) <= 1){ + $length -= 4; + } + + // we've got a match! + // or let's see if there's a higher version number available + if($length <= $this->maxBitsForEcc[$provisionalVersion] || isset($this->maxBitsForEcc[($provisionalVersion + 1)])){ + return $length; + } + + } + + throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length)); + } + + /** + * returns the minimum version number for the given string + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function getMinimumVersion():Version{ + + if($this->options->version !== Version::AUTO){ + return new Version($this->options->version); + } + + $total = $this->estimateTotalBitLength(); + + // guess the version number within the given range + for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){ + if($total <= ($this->maxBitsForEcc[$version] - 4)){ + return new Version($version); + } + } + + // it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first + throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore + } + + /** + * creates a BitBuffer and writes the string data to it + * + * @throws \chillerlan\QRCode\QRCodeException on data overflow + */ + private function writeBitBuffer():void{ + $MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version); + + foreach($this->dataSegments as $segment){ + $segment->write($this->bitBuffer, $this->version->getVersionNumber()); + } + + // overflow, likely caused due to invalid version setting + if($this->bitBuffer->getLength() > $MAX_BITS){ + throw new QRCodeDataException( + sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS) + ); + } + + // add terminator (ISO/IEC 18004:2000 Table 2) + if(($this->bitBuffer->getLength() + 4) <= $MAX_BITS){ + $this->bitBuffer->put(Mode::TERMINATOR, 4); + } + + // Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion + + // if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long + // by the addition of padding bits with binary value 0 + while(($this->bitBuffer->getLength() % 8) !== 0){ + + if($this->bitBuffer->getLength() === $MAX_BITS){ + break; + } + + $this->bitBuffer->putBit(false); + } + + // The message bit stream shall then be extended to fill the data capacity of the symbol + // corresponding to the Version and Error Correction Level, by the addition of the Pad + // Codewords 11101100 and 00010001 alternately. + $alternate = false; + + while(($this->bitBuffer->getLength() + 8) <= $MAX_BITS){ + $this->bitBuffer->put(($alternate) ? 0b00010001 : 0b11101100, 8); + + $alternate = !$alternate; + } + + // In certain versions of symbol, it may be necessary to add 3, 4 or 7 Remainder Bits (all zeros) + // to the end of the message in order exactly to fill the symbol capacity + while($this->bitBuffer->getLength() <= $MAX_BITS){ + $this->bitBuffer->putBit(false); + } + + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php new file mode 100644 index 000000000..94b93ac0e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php @@ -0,0 +1,61 @@ + + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\Mode; + +/** + * abstract methods for the several data modes + */ +abstract class QRDataModeAbstract implements QRDataModeInterface{ + + /** + * The data to write + */ + protected string $data; + + /** + * QRDataModeAbstract constructor. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function __construct(string $data){ + $data = $this::convertEncoding($data); + + if(!$this::validateString($data)){ + throw new QRCodeDataException('invalid data'); + } + + $this->data = $data; + } + + /** + * returns the character count of the $data string + */ + protected function getCharCount():int{ + return strlen($this->data); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + return $string; + } + + /** + * shortcut + */ + protected static function getLengthBits(int $versionNumber):int{ + return Mode::getLengthBitsForVersion(static::DATAMODE, $versionNumber); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php new file mode 100644 index 000000000..321cf60b6 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php @@ -0,0 +1,63 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\BitBuffer; + +/** + * Specifies the methods reqired for the data modules (Number, Alphanum, Byte and Kanji) + */ +interface QRDataModeInterface{ + + /** + * the current data mode: Number, Alphanum, Kanji, Hanzi, Byte, ECI + * + * tbh I hate this constant here, but it's part of the interface, so I can't just declare it in the abstract class. + * (phan will complain about a PhanAccessOverridesFinalConstant) + * + * @see https://wiki.php.net/rfc/final_class_const + * + * @var int + * @see \chillerlan\QRCode\Common\Mode + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const DATAMODE = -1; + + /** + * retruns the length in bits of the data string + */ + public function getLengthInBits():int; + + /** + * encoding conversion helper + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function convertEncoding(string $string):string; + + /** + * checks if the given string qualifies for the encoder module + */ + public static function validateString(string $string):bool; + + /** + * writes the actual data string to the BitBuffer, uses the given version to determine the length bits + * + * @see \chillerlan\QRCode\Data\QRData::writeBitBuffer() + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface; + + /** + * reads a segment from the BitBuffer and decodes in the current data mode + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php b/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php new file mode 100644 index 000000000..d92c1a784 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php @@ -0,0 +1,812 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version}; +use function array_fill, array_map, array_reverse, count, intdiv; + +/** + * Holds an array representation of the final QR Code that contains numerical values for later output modifications; + * maps the ECC coded binary data and applies the mask pattern + * + * @see http://www.thonky.com/qr-code-tutorial/format-version-information + */ +class QRMatrix{ + + /* + * special values + */ + + /** @var int */ + public const IS_DARK = 0b100000000000; + /** @var int */ + public const M_NULL = 0b000000000000; + /** @var int */ + public const M_LOGO = 0b001000000000; + /** @var int */ + public const M_LOGO_DARK = 0b101000000000; + + /* + * light values + */ + + /** @var int */ + public const M_DATA = 0b000000000010; + /** @var int */ + public const M_FINDER = 0b000000000100; + /** @var int */ + public const M_SEPARATOR = 0b000000001000; + /** @var int */ + public const M_ALIGNMENT = 0b000000010000; + /** @var int */ + public const M_TIMING = 0b000000100000; + /** @var int */ + public const M_FORMAT = 0b000001000000; + /** @var int */ + public const M_VERSION = 0b000010000000; + /** @var int */ + public const M_QUIETZONE = 0b000100000000; + + /* + * dark values + */ + + /** @var int */ + public const M_DARKMODULE = 0b100000000001; + /** @var int */ + public const M_DATA_DARK = 0b100000000010; + /** @var int */ + public const M_FINDER_DARK = 0b100000000100; + /** @var int */ + public const M_ALIGNMENT_DARK = 0b100000010000; + /** @var int */ + public const M_TIMING_DARK = 0b100000100000; + /** @var int */ + public const M_FORMAT_DARK = 0b100001000000; + /** @var int */ + public const M_VERSION_DARK = 0b100010000000; + /** @var int */ + public const M_FINDER_DOT = 0b110000000000; + + /* + * values used for reversed reflectance + */ + + /** @var int */ + public const M_DARKMODULE_LIGHT = 0b000000000001; + /** @var int */ + public const M_FINDER_DOT_LIGHT = 0b010000000000; + /** @var int */ + public const M_SEPARATOR_DARK = 0b100000001000; + /** @var int */ + public const M_QUIETZONE_DARK = 0b100100000000; + + /** + * Map of flag => coord + * + * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours() + * + * @var array + */ + protected const neighbours = [ + 0b00000001 => [-1, -1], + 0b00000010 => [ 0, -1], + 0b00000100 => [ 1, -1], + 0b00001000 => [ 1, 0], + 0b00010000 => [ 1, 1], + 0b00100000 => [ 0, 1], + 0b01000000 => [-1, 1], + 0b10000000 => [-1, 0], + ]; + + /** + * the matrix version - always set in QRMatrix, may be null in BitMatrix + */ + protected ?Version $version = null; + + /** + * the current ECC level - always set in QRMatrix, may be null in BitMatrix + */ + protected ?EccLevel $eccLevel = null; + + /** + * the mask pattern that was used in the most recent operation, set via: + * + * - QRMatrix::setFormatInfo() + * - QRMatrix::mask() + * - BitMatrix::readFormatInformation() + */ + protected ?MaskPattern $maskPattern = null; + + /** + * the size (side length) of the matrix, including quiet zone (if created) + */ + protected int $moduleCount; + + /** + * the actual matrix data array + * + * @var int[][] + */ + protected array $matrix; + + /** + * QRMatrix constructor. + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + $this->moduleCount = $this->version->getDimension(); + $this->matrix = $this->createMatrix($this->moduleCount, $this::M_NULL); + } + + /** + * Creates a 2-dimensional array (square) of the given $size + */ + protected function createMatrix(int $size, int $value):array{ + return array_fill(0, $size, array_fill(0, $size, $value)); + } + + /** + * shortcut to initialize the functional patterns + */ + public function initFunctionalPatterns():self{ + return $this + ->setFinderPattern() + ->setSeparators() + ->setAlignmentPattern() + ->setTimingPattern() + ->setDarkModule() + ->setVersionNumber() + ->setFormatInfo() + ; + } + + /** + * Returns the data matrix, returns a pure boolean representation if $boolean is set to true + * + * @return int[][]|bool[][] + */ + public function getMatrix(bool $boolean = null):array{ + + if($boolean !== true){ + return $this->matrix; + } + + $matrix = $this->matrix; + + foreach($matrix as &$row){ + $row = array_map([$this, 'isDark'], $row); + } + + return $matrix; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getMatrix() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMatrix() + * @codeCoverageIgnore + */ + public function matrix(bool $boolean = null):array{ + return $this->getMatrix($boolean); + } + + /** + * Returns the current version number + */ + public function getVersion():?Version{ + return $this->version; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getVersion() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getVersion() + * @codeCoverageIgnore + */ + public function version():?Version{ + return $this->getVersion(); + } + + /** + * Returns the current ECC level + */ + public function getEccLevel():?EccLevel{ + return $this->eccLevel; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getEccLevel() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getEccLevel() + * @codeCoverageIgnore + */ + public function eccLevel():?EccLevel{ + return $this->getEccLevel(); + } + + /** + * Returns the current mask pattern + */ + public function getMaskPattern():?MaskPattern{ + return $this->maskPattern; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getMaskPattern() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMaskPattern() + * @codeCoverageIgnore + */ + public function maskPattern():?MaskPattern{ + return $this->getMaskPattern(); + } + + /** + * Returns the absoulute size of the matrix, including quiet zone (after setting it). + * + * size = version * 4 + 17 [ + 2 * quietzone size] + */ + public function getSize():int{ + return $this->moduleCount; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getSize() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() + * @codeCoverageIgnore + */ + public function size():int{ + return $this->getSize(); + } + + /** + * Returns the value of the module at position [$x, $y] or -1 if the coordinate is outside the matrix + */ + public function get(int $x, int $y):int{ + + if(!isset($this->matrix[$y][$x])){ + return -1; + } + + return $this->matrix[$y][$x]; + } + + /** + * Sets the $M_TYPE value for the module at position [$x, $y] + * + * true => $M_TYPE | 0x800 + * false => $M_TYPE + */ + public function set(int $x, int $y, bool $value, int $M_TYPE):self{ + + if(isset($this->matrix[$y][$x])){ + // we don't know whether the input is dark, so we remove the dark bit + $M_TYPE &= ~$this::IS_DARK; + + if($value === true){ + $M_TYPE |= $this::IS_DARK; + } + + $this->matrix[$y][$x] = $M_TYPE; + } + + return $this; + } + + /** + * Fills an area of $width * $height, from the given starting point [$startX, $startY] (top left) with $value for $M_TYPE. + */ + public function setArea(int $startX, int $startY, int $width, int $height, bool $value, int $M_TYPE):self{ + + for($y = $startY; $y < ($startY + $height); $y++){ + for($x = $startX; $x < ($startX + $width); $x++){ + $this->set($x, $y, $value, $M_TYPE); + } + } + + return $this; + } + + /** + * Flips the value of the module at ($x, $y) + */ + public function flip(int $x, int $y):self{ + + if(isset($this->matrix[$y][$x])){ + $this->matrix[$y][$x] ^= $this::IS_DARK; + } + + return $this; + } + + /** + * Checks whether the module at ($x, $y) is of the given $M_TYPE + * + * true => $value & $M_TYPE === $M_TYPE + * + * Also, returns false if the given coordinates are out of range. + */ + public function checkType(int $x, int $y, int $M_TYPE):bool{ + + if(isset($this->matrix[$y][$x])){ + return ($this->matrix[$y][$x] & $M_TYPE) === $M_TYPE; + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is in the given array of $M_TYPES, + * returns true if a match is found, otherwise false. + */ + public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{ + + foreach($M_TYPES as $type){ + if($this->checkType($x, $y, $type)){ + return true; + } + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is true (dark) or false (light) + * + * Also, returns false if the given coordinates are out of range. + */ + public function check(int $x, int $y):bool{ + + if(isset($this->matrix[$y][$x])){ + return $this->isDark($this->matrix[$y][$x]); + } + + return false; + } + + /** + * Checks whether the given $M_TYPE is a dark value + */ + public function isDark(int $M_TYPE):bool{ + return ($M_TYPE & $this::IS_DARK) === $this::IS_DARK; + } + + /** + * Checks the status of the neighbouring modules for the module at ($x, $y) and returns a bitmask with the results. + * + * The 8 flags of the bitmask represent the status of each of the neighbouring fields, + * starting with the lowest bit for top left, going clockwise: + * + * 0 1 2 + * 7 # 3 + * 6 5 4 + */ + public function checkNeighbours(int $x, int $y, int $M_TYPE = null):int{ + $bits = 0; + + foreach($this::neighbours as $bit => [$ix, $iy]){ + $ix += $x; + $iy += $y; + + // $M_TYPE is given, skip if the field is not the same type + if($M_TYPE !== null && !$this->checkType($ix, $iy, $M_TYPE)){ + continue; + } + + if($this->checkType($ix, $iy, $this::IS_DARK)){ + $bits |= $bit; + } + } + + return $bits; + } + + /** + * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder + * + * 4 * version + 9 or moduleCount - 8 + */ + public function setDarkModule():self{ + $this->set(8, ($this->moduleCount - 8), true, $this::M_DARKMODULE); + + return $this; + } + + /** + * Draws the 7x7 finder patterns in the corners top left/right and bottom left + * + * ISO/IEC 18004:2000 Section 7.3.2 + */ + public function setFinderPattern():self{ + + $pos = [ + [0, 0], // top left + [($this->moduleCount - 7), 0], // top right + [0, ($this->moduleCount - 7)], // bottom left + ]; + + foreach($pos as $c){ + $this + ->setArea( $c[0] , $c[1] , 7, 7, true, $this::M_FINDER) + ->setArea(($c[0] + 1), ($c[1] + 1), 5, 5, false, $this::M_FINDER) + ->setArea(($c[0] + 2), ($c[1] + 2), 3, 3, true, $this::M_FINDER_DOT) + ; + } + + return $this; + } + + /** + * Draws the separator lines around the finder patterns + * + * ISO/IEC 18004:2000 Section 7.3.3 + */ + public function setSeparators():self{ + + $h = [ + [7, 0], + [($this->moduleCount - 8), 0], + [7, ($this->moduleCount - 8)], + ]; + + $v = [ + [7, 7], + [($this->moduleCount - 1), 7], + [7, ($this->moduleCount - 8)], + ]; + + for($c = 0; $c < 3; $c++){ + for($i = 0; $i < 8; $i++){ + $this->set( $h[$c][0] , ($h[$c][1] + $i), false, $this::M_SEPARATOR); + $this->set(($v[$c][0] - $i), $v[$c][1] , false, $this::M_SEPARATOR); + } + } + + return $this; + } + + + /** + * Draws the 5x5 alignment patterns + * + * ISO/IEC 18004:2000 Section 7.3.5 + */ + public function setAlignmentPattern():self{ + $alignmentPattern = $this->version->getAlignmentPattern(); + + foreach($alignmentPattern as $y){ + foreach($alignmentPattern as $x){ + + // skip existing patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; + } + + $this + ->setArea(($x - 2), ($y - 2), 5, 5, true, $this::M_ALIGNMENT) + ->setArea(($x - 1), ($y - 1), 3, 3, false, $this::M_ALIGNMENT) + ->set($x, $y, true, $this::M_ALIGNMENT) + ; + + } + } + + return $this; + } + + + /** + * Draws the timing pattern (h/v checkered line between the finder patterns) + * + * ISO/IEC 18004:2000 Section 7.3.4 + */ + public function setTimingPattern():self{ + + for($i = 8; $i < ($this->moduleCount - 8); $i++){ + + if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){ + continue; + } + + $v = ($i % 2) === 0; + + $this->set($i, 6, $v, $this::M_TIMING); // h + $this->set(6, $i, $v, $this::M_TIMING); // v + } + + return $this; + } + + /** + * Draws the version information, 2x 3x6 pixel + * + * ISO/IEC 18004:2000 Section 8.10 + */ + public function setVersionNumber():self{ + $bits = $this->version->getVersionPattern(); + + if($bits !== null){ + + for($i = 0; $i < 18; $i++){ + $a = intdiv($i, 3); + $b = (($i % 3) + ($this->moduleCount - 8 - 3)); + $v = (($bits >> $i) & 1) === 1; + + $this->set($b, $a, $v, $this::M_VERSION); // ne + $this->set($a, $b, $v, $this::M_VERSION); // sw + } + + } + + return $this; + } + + /** + * Draws the format info along the finder patterns. If no $maskPattern, all format info modules will be set to false. + * + * ISO/IEC 18004:2000 Section 8.9 + */ + public function setFormatInfo(MaskPattern $maskPattern = null):self{ + $this->maskPattern = $maskPattern; + $bits = 0; // sets all format fields to false (test mode) + + if($this->maskPattern instanceof MaskPattern){ + $bits = $this->eccLevel->getformatPattern($this->maskPattern); + } + + for($i = 0; $i < 15; $i++){ + $v = (($bits >> $i) & 1) === 1; + + if($i < 6){ + $this->set(8, $i, $v, $this::M_FORMAT); + } + elseif($i < 8){ + $this->set(8, ($i + 1), $v, $this::M_FORMAT); + } + else{ + $this->set(8, ($this->moduleCount - 15 + $i), $v, $this::M_FORMAT); + } + + if($i < 8){ + $this->set(($this->moduleCount - $i - 1), 8, $v, $this::M_FORMAT); + } + elseif($i < 9){ + $this->set(((15 - $i)), 8, $v, $this::M_FORMAT); + } + else{ + $this->set((15 - $i - 1), 8, $v, $this::M_FORMAT); + } + + } + + return $this; + } + + /** + * Draws the "quiet zone" of $size around the matrix + * + * ISO/IEC 18004:2000 Section 7.3.7 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setQuietZone(int $quietZoneSize):self{ + + // early exit if there's nothing to add + if($quietZoneSize < 1){ + return $this; + } + + if($this->matrix[($this->moduleCount - 1)][($this->moduleCount - 1)] === $this::M_NULL){ + throw new QRCodeDataException('use only after writing data'); + } + + // create a matrix with the new size + $newSize = ($this->moduleCount + ($quietZoneSize * 2)); + $newMatrix = $this->createMatrix($newSize, $this::M_QUIETZONE); + + // copy over the current matrix + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + $newMatrix[($y + $quietZoneSize)][($x + $quietZoneSize)] = $val; + } + } + + // set the new values + $this->moduleCount = $newSize; + $this->matrix = $newMatrix; + + return $this; + } + + /** + * Rotates the matrix by 90 degrees clock wise + */ + public function rotate90():self{ + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix); + + return $this; + } + + /** + * Inverts the values of the whole matrix + * + * ISO/IEC 18004:2015 Section 6.2 - Reflectance reversal + */ + public function invert():self{ + + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + + // skip null fields + if($val === $this::M_NULL){ + continue; + } + + $this->flip($x, $y); + } + } + + return $this; + } + + /** + * Clears a space of $width * $height in order to add a logo or text. + * If no $height is given, the space will be assumed a square of $width. + * + * Additionally, the logo space can be positioned within the QR Code using $startX and $startY. + * If either of these are null, the logo space will be centered in that direction. + * ECC level "H" (30%) is required. + * + * The coordinates of $startX and $startY do not include the quiet zone: + * [0, 0] is always the top left module of the top left finder pattern, negative values go into the quiet zone top and left. + * + * Please note that adding a logo space minimizes the error correction capacity of the QR Code and + * created images may become unreadable, especially when printed with a chance to receive damage. + * Please test thoroughly before using this feature in production. + * + * This method should be called from within an output module (after the matrix has been filled with data). + * Note that there is no restiction on how many times this method could be called on the same matrix instance. + * + * @link https://github.com/chillerlan/php-qrcode/issues/52 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ + $height ??= $width; + + // if width and height happen to be negative or 0 (default value), just return - nothing to do + if($width <= 0 || $height <= 0){ + return $this; // @codeCoverageIgnore + } + + // for logos, we operate in ECC H (30%) only + if($this->eccLevel->getLevel() !== EccLevel::H){ + throw new QRCodeDataException('ECC level "H" required to add logo space'); + } + + // $this->moduleCount includes the quiet zone (if created), we need the QR size here + $dimension = $this->version->getDimension(); + + // throw if the size exceeds the qrcode size + if($width > $dimension || $height > $dimension){ + throw new QRCodeDataException('logo dimensions exceed matrix size'); + } + + // we need uneven sizes to center the logo space, adjust if needed + if($startX === null && ($width % 2) === 0){ + $width++; + } + + if($startY === null && ($height % 2) === 0){ + $height++; + } + + // throw if the logo space exceeds the maximum error correction capacity + if(($width * $height) > (int)($dimension * $dimension * 0.25)){ + throw new QRCodeDataException('logo space exceeds the maximum error correction capacity'); + } + + $quietzone = (($this->moduleCount - $dimension) / 2); + $end = ($this->moduleCount - $quietzone); + + // determine start coordinates + $startX ??= (($dimension - $width) / 2); + $startY ??= (($dimension - $height) / 2); + $endX = ($quietzone + $startX + $width); + $endY = ($quietzone + $startY + $height); + + // clear the space + for($y = ($quietzone + $startY); $y < $endY; $y++){ + for($x = ($quietzone + $startX); $x < $endX; $x++){ + // out of bounds, skip + if($x < $quietzone || $y < $quietzone ||$x >= $end || $y >= $end){ + continue; + } + + $this->set($x, $y, false, $this::M_LOGO); + } + } + + return $this; + } + + /** + * Maps the interleaved binary $data on the matrix + */ + public function writeCodewords(BitBuffer $bitBuffer):self{ + $data = (new ReedSolomonEncoder($this->version, $this->eccLevel))->interleaveEcBytes($bitBuffer); + $byteCount = count($data); + $iByte = 0; + $iBit = 7; + $direction = true; + + for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ + + // skip vertical alignment pattern + if($i === 6){ + $i--; + } + + for($count = 0; $count < $this->moduleCount; $count++){ + $y = $count; + + if($direction){ + $y = ($this->moduleCount - 1 - $count); + } + + for($col = 0; $col < 2; $col++){ + $x = ($i - $col); + + // skip functional patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; + } + + $this->matrix[$y][$x] = $this::M_DATA; + + if($iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1){ + $this->matrix[$y][$x] |= $this::IS_DARK; + } + + if($iBit === -1){ + $iByte++; + $iBit = 7; + } + } + } + + $direction = !$direction; // switch directions + } + + return $this; + } + + /** + * Applies/reverses the mask pattern + * + * ISO/IEC 18004:2000 Section 8.8.1 + */ + public function mask(MaskPattern $maskPattern):self{ + $this->maskPattern = $maskPattern; + $mask = $this->maskPattern->getMask(); + + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + // skip non-data modules + if(($val & $this::M_DATA) === $this::M_DATA && $mask($x, $y)){ + $this->flip($x, $y); + } + } + } + + return $this; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php b/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php new file mode 100644 index 000000000..60444378c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php @@ -0,0 +1,127 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version}; +use function array_fill, array_merge, count, max; + +/** + * Reed-Solomon encoding - ISO/IEC 18004:2000 Section 8.5 ff + * + * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding + */ +final class ReedSolomonEncoder{ + + private Version $version; + private EccLevel $eccLevel; + + private array $interleavedData; + private int $interleavedDataIndex; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + + /** + * ECC encoding and interleaving + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function interleaveEcBytes(BitBuffer $bitBuffer):array{ + [$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $this->version->getRSBlocks($this->eccLevel); + + $rsBlocks = array_fill(0, $l1, [($numEccCodewords + $b1), $b1]); + + if($l2 > 0){ + $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [($numEccCodewords + $b2), $b2])); + } + + $bitBufferData = $bitBuffer->getBuffer(); + $dataBytes = []; + $ecBytes = []; + $maxDataBytes = 0; + $maxEcBytes = 0; + $dataByteOffset = 0; + + foreach($rsBlocks as $key => [$rsBlockTotal, $dataByteCount]){ + $dataBytes[$key] = []; + + for($i = 0; $i < $dataByteCount; $i++){ + $dataBytes[$key][$i] = ($bitBufferData[($i + $dataByteOffset)] & 0xff); + } + + $ecByteCount = ($rsBlockTotal - $dataByteCount); + $ecBytes[$key] = $this->encode($dataBytes[$key], $ecByteCount); + $maxDataBytes = max($maxDataBytes, $dataByteCount); + $maxEcBytes = max($maxEcBytes, $ecByteCount); + $dataByteOffset += $dataByteCount; + } + + $this->interleavedData = array_fill(0, $this->version->getTotalCodewords(), 0); + $this->interleavedDataIndex = 0; + $numRsBlocks = ($l1 + $l2); + + $this->interleave($dataBytes, $maxDataBytes, $numRsBlocks); + $this->interleave($ecBytes, $maxEcBytes, $numRsBlocks); + + return $this->interleavedData; + } + + /** + * + */ + private function encode(array $dataBytes, int $ecByteCount):array{ + $rsPoly = new GenericGFPoly([1]); + + for($i = 0; $i < $ecByteCount; $i++){ + $rsPoly = $rsPoly->multiply(new GenericGFPoly([1, GF256::exp($i)])); + } + + $rsPolyDegree = $rsPoly->getDegree(); + + $modCoefficients = (new GenericGFPoly($dataBytes, $rsPolyDegree)) + ->mod($rsPoly) + ->getCoefficients() + ; + + $ecBytes = array_fill(0, $rsPolyDegree, 0); + $count = (count($modCoefficients) - $rsPolyDegree); + + foreach($ecBytes as $i => &$val){ + $modIndex = ($i + $count); + $val = 0; + + if($modIndex >= 0){ + $val = $modCoefficients[$modIndex]; + } + } + + return $ecBytes; + } + + /** + * + */ + private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{ + for($x = 0; $x < $maxBytes; $x++){ + for($y = 0; $y < $numRsBlocks; $y++){ + if($x < count($byteArray[$y])){ + $this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x]; + } + } + } + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php new file mode 100644 index 000000000..7b7b49f65 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php @@ -0,0 +1,361 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\LuminanceSourceInterface; +use chillerlan\QRCode\Data\QRMatrix; +use function array_fill, count, intdiv, max; + +/** + * This class implements a local thresholding algorithm, which while slower than the + * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for + * high frequency images of barcodes with black data on white backgrounds. For this application, + * it does a much better job than a global blackpoint with severe shadows and gradients. + * However, it tends to produce artifacts on lower frequency images and is therefore not + * a good general purpose binarizer for uses outside ZXing. + * + * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, + * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already + * inherently local, and only fails for horizontal gradients. We can revisit that problem later, + * but for now it was not a win to use local blocks for 1D. + * + * This Binarizer is the default for the unit tests and the recommended class for library users. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class Binarizer{ + + // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. + // So this is the smallest dimension in each axis we can accept. + private const BLOCK_SIZE_POWER = 3; + private const BLOCK_SIZE = 8; // ...0100...00 + private const BLOCK_SIZE_MASK = 7; // ...0011...11 + private const MINIMUM_DIMENSION = 40; + private const MIN_DYNAMIC_RANGE = 24; + +# private const LUMINANCE_BITS = 5; + private const LUMINANCE_SHIFT = 3; + private const LUMINANCE_BUCKETS = 32; + + private LuminanceSourceInterface $source; + private array $luminances; + + /** + * + */ + public function __construct(LuminanceSourceInterface $source){ + $this->source = $source; + $this->luminances = $this->source->getLuminances(); + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function estimateBlackPoint(array $buckets):int{ + // Find the tallest peak in the histogram. + $numBuckets = count($buckets); + $maxBucketCount = 0; + $firstPeak = 0; + $firstPeakSize = 0; + + for($x = 0; $x < $numBuckets; $x++){ + + if($buckets[$x] > $firstPeakSize){ + $firstPeak = $x; + $firstPeakSize = $buckets[$x]; + } + + if($buckets[$x] > $maxBucketCount){ + $maxBucketCount = $buckets[$x]; + } + } + + // Find the second-tallest peak which is somewhat far from the tallest peak. + $secondPeak = 0; + $secondPeakScore = 0; + + for($x = 0; $x < $numBuckets; $x++){ + $distanceToBiggest = ($x - $firstPeak); + // Encourage more distant second peaks by multiplying by square of distance. + $score = ($buckets[$x] * $distanceToBiggest * $distanceToBiggest); + + if($score > $secondPeakScore){ + $secondPeak = $x; + $secondPeakScore = $score; + } + } + + // Make sure firstPeak corresponds to the black peak. + if($firstPeak > $secondPeak){ + $temp = $firstPeak; + $firstPeak = $secondPeak; + $secondPeak = $temp; + } + + // If there is too little contrast in the image to pick a meaningful black point, throw rather + // than waste time trying to decode the image, and risk false positives. + if(($secondPeak - $firstPeak) <= ($numBuckets / 16)){ + throw new QRCodeDecoderException('no meaningful dark point found'); // @codeCoverageIgnore + } + + // Find a valley between them that is low and closer to the white peak. + $bestValley = ($secondPeak - 1); + $bestValleyScore = -1; + + for($x = ($secondPeak - 1); $x > $firstPeak; $x--){ + $fromFirst = ($x - $firstPeak); + $score = ($fromFirst * $fromFirst * ($secondPeak - $x) * ($maxBucketCount - $buckets[$x])); + + if($score > $bestValleyScore){ + $bestValley = $x; + $bestValleyScore = $score; + } + } + + return ($bestValley << self::LUMINANCE_SHIFT); + } + + /** + * Calculates the final BitMatrix once for all requests. This could be called once from the + * constructor instead, but there are some advantages to doing it lazily, such as making + * profiling easier, and not doing heavy lifting when callers don't expect it. + * + * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive + * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or + * may not apply sharpening. Therefore, a row from this matrix may not be identical to one + * fetched using getBlackRow(), so don't mix and match between them. + * + * @return \chillerlan\QRCode\Decoder\BitMatrix The 2D array of bits for the image (true means black). + */ + public function getBlackMatrix():BitMatrix{ + $width = $this->source->getWidth(); + $height = $this->source->getHeight(); + + if($width >= self::MINIMUM_DIMENSION && $height >= self::MINIMUM_DIMENSION){ + $subWidth = ($width >> self::BLOCK_SIZE_POWER); + + if(($width & self::BLOCK_SIZE_MASK) !== 0){ + $subWidth++; + } + + $subHeight = ($height >> self::BLOCK_SIZE_POWER); + + if(($height & self::BLOCK_SIZE_MASK) !== 0){ + $subHeight++; + } + + return $this->calculateThresholdForBlock($subWidth, $subHeight, $width, $height); + } + + // If the image is too small, fall back to the global histogram approach. + return $this->getHistogramBlackMatrix($width, $height); + } + + /** + * + */ + private function getHistogramBlackMatrix(int $width, int $height):BitMatrix{ + + // Quickly calculates the histogram by sampling four rows from the image. This proved to be + // more robust on the blackbox tests than sampling a diagonal as we used to do. + $buckets = array_fill(0, self::LUMINANCE_BUCKETS, 0); + $right = intdiv(($width * 4), 5); + $x = intdiv($width, 5); + + for($y = 1; $y < 5; $y++){ + $row = intdiv(($height * $y), 5); + $localLuminances = $this->source->getRow($row); + + for(; $x < $right; $x++){ + $pixel = ($localLuminances[$x] & 0xff); + $buckets[($pixel >> self::LUMINANCE_SHIFT)]++; + } + } + + $blackPoint = $this->estimateBlackPoint($buckets); + + // We delay reading the entire image luminance until the black point estimation succeeds. + // Although we end up reading four rows twice, it is consistent with our motto of + // "fail quickly" which is necessary for continuous scanning. + $matrix = new BitMatrix(max($width, $height)); + + for($y = 0; $y < $height; $y++){ + $offset = ($y * $width); + + for($x = 0; $x < $width; $x++){ + $matrix->set($x, $y, (($this->luminances[($offset + $x)] & 0xff) < $blackPoint), QRMatrix::M_DATA); + } + } + + return $matrix; + } + + /** + * Calculates a single black point for each block of pixels and saves it away. + * See the following thread for a discussion of this algorithm: + * + * @see http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + */ + private function calculateBlackPoints(int $subWidth, int $subHeight, int $width, int $height):array{ + $blackPoints = array_fill(0, $subHeight, array_fill(0, $subWidth, 0)); + + for($y = 0; $y < $subHeight; $y++){ + $yoffset = ($y << self::BLOCK_SIZE_POWER); + $maxYOffset = ($height - self::BLOCK_SIZE); + + if($yoffset > $maxYOffset){ + $yoffset = $maxYOffset; + } + + for($x = 0; $x < $subWidth; $x++){ + $xoffset = ($x << self::BLOCK_SIZE_POWER); + $maxXOffset = ($width - self::BLOCK_SIZE); + + if($xoffset > $maxXOffset){ + $xoffset = $maxXOffset; + } + + $sum = 0; + $min = 255; + $max = 0; + + for($yy = 0, $offset = ($yoffset * $width + $xoffset); $yy < self::BLOCK_SIZE; $yy++, $offset += $width){ + + for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){ + $pixel = ((int)($this->luminances[(int)($offset + $xx)]) & 0xff); + $sum += $pixel; + // still looking for good contrast + if($pixel < $min){ + $min = $pixel; + } + + if($pixel > $max){ + $max = $pixel; + } + } + + // short-circuit min/max tests once dynamic range is met + if(($max - $min) > self::MIN_DYNAMIC_RANGE){ + // finish the rest of the rows quickly + for($yy++, $offset += $width; $yy < self::BLOCK_SIZE; $yy++, $offset += $width){ + for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){ + $sum += ((int)($this->luminances[(int)($offset + $xx)]) & 0xff); + } + } + } + } + + // The default estimate is the average of the values in the block. + $average = ($sum >> (self::BLOCK_SIZE_POWER * 2)); + + if(($max - $min) <= self::MIN_DYNAMIC_RANGE){ + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + $average = ($min / 2); + + if($y > 0 && $x > 0){ + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + $averageNeighborBlackPoint = ( + ($blackPoints[($y - 1)][$x] + (2 * $blackPoints[$y][($x - 1)]) + $blackPoints[($y - 1)][($x - 1)]) / 4 + ); + + if($min < $averageNeighborBlackPoint){ + $average = $averageNeighborBlackPoint; + } + } + } + + $blackPoints[$y][$x] = $average; + } + } + + return $blackPoints; + } + + /** + * For each block in the image, calculate the average black point using a 5x5 grid + * of the surrounding blocks. Also handles the corner cases (fractional blocks are computed based + * on the last pixels in the row/column which are also used in the previous block). + */ + private function calculateThresholdForBlock(int $subWidth, int $subHeight, int $width, int $height):BitMatrix{ + $matrix = new BitMatrix(max($width, $height)); + $blackPoints = $this->calculateBlackPoints($subWidth, $subHeight, $width, $height); + + for($y = 0; $y < $subHeight; $y++){ + $yoffset = ($y << self::BLOCK_SIZE_POWER); + $maxYOffset = ($height - self::BLOCK_SIZE); + + if($yoffset > $maxYOffset){ + $yoffset = $maxYOffset; + } + + for($x = 0; $x < $subWidth; $x++){ + $xoffset = ($x << self::BLOCK_SIZE_POWER); + $maxXOffset = ($width - self::BLOCK_SIZE); + + if($xoffset > $maxXOffset){ + $xoffset = $maxXOffset; + } + + $left = $this->cap($x, 2, ($subWidth - 3)); + $top = $this->cap($y, 2, ($subHeight - 3)); + $sum = 0; + + for($z = -2; $z <= 2; $z++){ + $br = $blackPoints[($top + $z)]; + $sum += ($br[($left - 2)] + $br[($left - 1)] + $br[$left] + $br[($left + 1)] + $br[($left + 2)]); + } + + $average = (int)($sum / 25); + + // Applies a single threshold to a block of pixels. + for($j = 0, $o = ($yoffset * $width + $xoffset); $j < self::BLOCK_SIZE; $j++, $o += $width){ + for($i = 0; $i < self::BLOCK_SIZE; $i++){ + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. + $v = (((int)($this->luminances[($o + $i)]) & 0xff) <= $average); + + $matrix->set(($xoffset + $i), ($yoffset + $j), $v, QRMatrix::M_DATA); + } + } + } + } + + return $matrix; + } + + /** + * @noinspection PhpSameParameterValueInspection + */ + private function cap(int $value, int $min, int $max):int{ + + if($value < $min){ + return $min; + } + + if($value > $max){ + return $max; + } + + return $value; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php new file mode 100644 index 000000000..ea2e321b5 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php @@ -0,0 +1,430 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version}; +use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix}; +use function array_fill, array_reverse, count; +use const PHP_INT_MAX, PHP_INT_SIZE; + +/** + * Extended QRMatrix to map read data from the Binarizer + */ +final class BitMatrix extends QRMatrix{ + + /** + * See ISO 18004:2006, Annex C, Table C.1 + * + * [data bits, sequence after masking] + */ + private const DECODE_LOOKUP = [ + 0x5412, // 0101010000010010 + 0x5125, // 0101000100100101 + 0x5E7C, // 0101111001111100 + 0x5B4B, // 0101101101001011 + 0x45F9, // 0100010111111001 + 0x40CE, // 0100000011001110 + 0x4F97, // 0100111110010111 + 0x4AA0, // 0100101010100000 + 0x77C4, // 0111011111000100 + 0x72F3, // 0111001011110011 + 0x7DAA, // 0111110110101010 + 0x789D, // 0111100010011101 + 0x662F, // 0110011000101111 + 0x6318, // 0110001100011000 + 0x6C41, // 0110110001000001 + 0x6976, // 0110100101110110 + 0x1689, // 0001011010001001 + 0x13BE, // 0001001110111110 + 0x1CE7, // 0001110011100111 + 0x19D0, // 0001100111010000 + 0x0762, // 0000011101100010 + 0x0255, // 0000001001010101 + 0x0D0C, // 0000110100001100 + 0x083B, // 0000100000111011 + 0x355F, // 0011010101011111 + 0x3068, // 0011000001101000 + 0x3F31, // 0011111100110001 + 0x3A06, // 0011101000000110 + 0x24B4, // 0010010010110100 + 0x2183, // 0010000110000011 + 0x2EDA, // 0010111011011010 + 0x2BED, // 0010101111101101 + ]; + + private const FORMAT_INFO_MASK_QR = 0x5412; // 0101010000010010 + + /** + * This flag has effect only on the copyVersionBit() method. + * Before proceeding with readCodewords() the resetInfo() method should be called. + */ + private bool $mirror = false; + + /** + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(int $dimension){ + $this->moduleCount = $dimension; + $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL)); + } + + /** + * Resets the current version info in order to attempt another reading + */ + public function resetVersionInfo():self{ + $this->version = null; + $this->eccLevel = null; + $this->maskPattern = null; + + return $this; + } + + /** + * Mirror the bit matrix diagonally in order to attempt a second reading. + */ + public function mirrorDiagonal():self{ + $this->mirror = !$this->mirror; + + // mirror vertically + $this->matrix = array_reverse($this->matrix); + // rotate by 90 degrees clockwise + /** @phan-suppress-next-line PhanTypeMismatchReturnSuperType */ + return $this->rotate90(); + } + + /** + * Reads the bits in the BitMatrix representing the finder pattern in the + * correct order in order to reconstruct the codewords bytes contained within the + * QR Code. Throws if the exact number of bytes expected is not read. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function readCodewords():array{ + + $this + ->readFormatInformation() + ->readVersion() + ->mask($this->maskPattern) // reverse the mask pattern + ; + + // invoke a fresh matrix with only the function & format patterns to compare against + $matrix = (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->setFormatInfo($this->maskPattern) + ; + + $result = []; + $byte = 0; + $bitsRead = 0; + $direction = true; + + // Read columns in pairs, from right to left + for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ + + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + if($i === 6){ + $i--; + } + // Read alternatingly from bottom to top then top to bottom + for($count = 0; $count < $this->moduleCount; $count++){ + $y = ($direction) ? ($this->moduleCount - 1 - $count) : $count; + + for($col = 0; $col < 2; $col++){ + $x = ($i - $col); + + // Ignore bits covered by the function pattern + if($matrix->get($x, $y) !== $this::M_NULL){ + continue; + } + + $bitsRead++; + $byte <<= 1; + + if($this->check($x, $y)){ + $byte |= 1; + } + // If we've made a whole byte, save it off + if($bitsRead === 8){ + $result[] = $byte; + $bitsRead = 0; + $byte = 0; + } + } + } + + $direction = !$direction; // switch directions + } + + if(count($result) !== $this->version->getTotalCodewords()){ + throw new QRCodeDecoderException('result count differs from total codewords for version'); + } + + // bytes encoded within the QR Code + return $result; + } + + /** + * Reads format information from one of its two locations within the QR Code. + * Throws if both format information locations cannot be parsed as the valid encoding of format information. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function readFormatInformation():self{ + + if($this->eccLevel !== null && $this->maskPattern !== null){ + return $this; + } + + // Read top-left format info bits + $formatInfoBits1 = 0; + + for($i = 0; $i < 6; $i++){ + $formatInfoBits1 = $this->copyVersionBit($i, 8, $formatInfoBits1); + } + + // ... and skip a bit in the timing pattern ... + $formatInfoBits1 = $this->copyVersionBit(7, 8, $formatInfoBits1); + $formatInfoBits1 = $this->copyVersionBit(8, 8, $formatInfoBits1); + $formatInfoBits1 = $this->copyVersionBit(8, 7, $formatInfoBits1); + // ... and skip a bit in the timing pattern ... + for($j = 5; $j >= 0; $j--){ + $formatInfoBits1 = $this->copyVersionBit(8, $j, $formatInfoBits1); + } + + // Read the top-right/bottom-left pattern too + $formatInfoBits2 = 0; + $jMin = ($this->moduleCount - 7); + + for($j = ($this->moduleCount - 1); $j >= $jMin; $j--){ + $formatInfoBits2 = $this->copyVersionBit(8, $j, $formatInfoBits2); + } + + for($i = ($this->moduleCount - 8); $i < $this->moduleCount; $i++){ + $formatInfoBits2 = $this->copyVersionBit($i, 8, $formatInfoBits2); + } + + $formatInfo = $this->doDecodeFormatInformation($formatInfoBits1, $formatInfoBits2); + + if($formatInfo === null){ + + // Should return null, but, some QR codes apparently do not mask this info. + // Try again by actually masking the pattern first. + $formatInfo = $this->doDecodeFormatInformation( + ($formatInfoBits1 ^ $this::FORMAT_INFO_MASK_QR), + ($formatInfoBits2 ^ $this::FORMAT_INFO_MASK_QR) + ); + + // still nothing??? + if($formatInfo === null){ + throw new QRCodeDecoderException('failed to read format info'); // @codeCoverageIgnore + } + + } + + $this->eccLevel = new EccLevel(($formatInfo >> 3) & 0x03); // Bits 3,4 + $this->maskPattern = new MaskPattern($formatInfo & 0x07); // Bottom 3 bits + + return $this; + } + + /** + * + */ + private function copyVersionBit(int $i, int $j, int $versionBits):int{ + + $bit = $this->mirror + ? $this->check($j, $i) + : $this->check($i, $j); + + return ($bit) ? (($versionBits << 1) | 0x1) : ($versionBits << 1); + } + + /** + * Returns information about the format it specifies, or null if it doesn't seem to match any known pattern + */ + private function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2):?int{ + $bestDifference = PHP_INT_MAX; + $bestFormatInfo = 0; + + // Find the int in FORMAT_INFO_DECODE_LOOKUP with the fewest bits differing + foreach($this::DECODE_LOOKUP as $maskedBits => $dataBits){ + + if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){ + // Found an exact match + return $maskedBits; + } + + $bitsDifference = $this->numBitsDiffering($maskedFormatInfo1, $dataBits); + + if($bitsDifference < $bestDifference){ + $bestFormatInfo = $maskedBits; + $bestDifference = $bitsDifference; + } + + if($maskedFormatInfo1 !== $maskedFormatInfo2){ + // also try the other option + $bitsDifference = $this->numBitsDiffering($maskedFormatInfo2, $dataBits); + + if($bitsDifference < $bestDifference){ + $bestFormatInfo = $maskedBits; + $bestDifference = $bitsDifference; + } + } + } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match + if($bestDifference <= 3){ + return $bestFormatInfo; + } + + return null; + } + + /** + * Reads version information from one of its two locations within the QR Code. + * Throws if both version information locations cannot be parsed as the valid encoding of version information. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + * @noinspection DuplicatedCode + */ + private function readVersion():self{ + + if($this->version !== null){ + return $this; + } + + $provisionalVersion = (($this->moduleCount - 17) / 4); + + // no version info if v < 7 + if($provisionalVersion < 7){ + $this->version = new Version($provisionalVersion); + + return $this; + } + + // Read top-right version info: 3 wide by 6 tall + $versionBits = 0; + $ijMin = ($this->moduleCount - 11); + + for($y = 5; $y >= 0; $y--){ + for($x = ($this->moduleCount - 9); $x >= $ijMin; $x--){ + $versionBits = $this->copyVersionBit($x, $y, $versionBits); + } + } + + $this->version = $this->decodeVersionInformation($versionBits); + + if($this->version !== null && $this->version->getDimension() === $this->moduleCount){ + return $this; + } + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + $versionBits = 0; + + for($x = 5; $x >= 0; $x--){ + for($y = ($this->moduleCount - 9); $y >= $ijMin; $y--){ + $versionBits = $this->copyVersionBit($x, $y, $versionBits); + } + } + + $this->version = $this->decodeVersionInformation($versionBits); + + if($this->version !== null && $this->version->getDimension() === $this->moduleCount){ + return $this; + } + + throw new QRCodeDecoderException('failed to read version'); + } + + /** + * Decodes the version information from the given bit sequence, returns null if no valid match is found. + */ + private function decodeVersionInformation(int $versionBits):?Version{ + $bestDifference = PHP_INT_MAX; + $bestVersion = 0; + + for($i = 7; $i <= 40; $i++){ + $targetVersion = new Version($i); + $targetVersionPattern = $targetVersion->getVersionPattern(); + + // Do the version info bits match exactly? done. + if($targetVersionPattern === $versionBits){ + return $targetVersion; + } + + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + /** @phan-suppress-next-line PhanTypeMismatchArgumentNullable ($targetVersionPattern is never null here) */ + $bitsDifference = $this->numBitsDiffering($versionBits, $targetVersionPattern); + + if($bitsDifference < $bestDifference){ + $bestVersion = $i; + $bestDifference = $bitsDifference; + } + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if($bestDifference <= 3){ + return new Version($bestVersion); + } + + // If we didn't find a close enough match, fail + return null; + } + + /** + * + */ + private function uRShift(int $a, int $b):int{ + + if($b === 0){ + return $a; + } + + return (($a >> $b) & ~((1 << (8 * PHP_INT_SIZE - 1)) >> ($b - 1))); + } + + /** + * + */ + private function numBitsDiffering(int $a, int $b):int{ + // a now has a 1 bit exactly where its bit differs with b's + $a ^= $b; + // Offset $i holds the number of 1-bits in the binary representation of $i + $BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; + // Count bits set quickly with a series of lookups: + $count = 0; + + for($i = 0; $i < 32; $i += 4){ + $count += $BITS_SET_IN_HALF_BYTE[($this->uRShift($a, $i) & 0x0F)]; + } + + return $count; + } + + /** + * @codeCoverageIgnore + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setQuietZone(int $quietZoneSize = null):self{ + throw new QRCodeDataException('not supported'); + } + + /** + * @codeCoverageIgnore + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ + throw new QRCodeDataException('not supported'); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php new file mode 100644 index 000000000..6f369a6df --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php @@ -0,0 +1,173 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, LuminanceSourceInterface, MaskPattern, Mode, Version}; +use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number}; +use chillerlan\QRCode\Detector\Detector; +use Throwable; +use function chr, str_replace; + +/** + * The main class which implements QR Code decoding -- as opposed to locating and extracting + * the QR Code from an image. + * + * @author Sean Owen + */ +final class Decoder{ + + private ?Version $version = null; + private ?EccLevel $eccLevel = null; + private ?MaskPattern $maskPattern = null; + private BitBuffer $bitBuffer; + + /** + * Decodes a QR Code represented as a BitMatrix. + * A 1 or "true" is taken to mean a black module. + * + * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + public function decode(LuminanceSourceInterface $source):DecoderResult{ + $matrix = (new Detector($source))->detect(); + + try{ + // clone the BitMatrix to avoid errors in case we run into mirroring + return $this->decodeMatrix(clone $matrix); + } + catch(Throwable $e){ + + try{ + /* + * Prepare for a mirrored reading. + * + * Since we're here, this means we have successfully detected some kind + * of version and format information when mirrored. This is a good sign, + * that the QR code may be mirrored, and we should try once more with a + * mirrored content. + */ + return $this->decodeMatrix($matrix->resetVersionInfo()->mirrorDiagonal()); + } + catch(Throwable $f){ + // Throw the exception from the original reading + throw $e; + } + + } + + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function decodeMatrix(BitMatrix $matrix):DecoderResult{ + // Read raw codewords + $rawCodewords = $matrix->readCodewords(); + $this->version = $matrix->getVersion(); + $this->eccLevel = $matrix->getEccLevel(); + $this->maskPattern = $matrix->getMaskPattern(); + + if($this->version === null || $this->eccLevel === null || $this->maskPattern === null){ + throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore + } + + $resultBytes = (new ReedSolomonDecoder($this->version, $this->eccLevel))->decode($rawCodewords); + + return $this->decodeBitStream($resultBytes); + } + + /** + * Decode the contents of that stream of bytes + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function decodeBitStream(BitBuffer $bitBuffer):DecoderResult{ + $this->bitBuffer = $bitBuffer; + $versionNumber = $this->version->getVersionNumber(); + $symbolSequence = -1; + $parityData = -1; + $fc1InEffect = false; + $result = ''; + + // While still another segment to read... + while($this->bitBuffer->available() >= 4){ + $datamode = $this->bitBuffer->read(4); // mode is encoded by 4 bits + + // OK, assume we're done + if($datamode === Mode::TERMINATOR){ + break; + } + elseif($datamode === Mode::NUMBER){ + $result .= Number::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::ALPHANUM){ + $result .= $this->decodeAlphanumSegment($versionNumber, $fc1InEffect); + } + elseif($datamode === Mode::BYTE){ + $result .= Byte::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::KANJI){ + $result .= Kanji::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::STRCTURED_APPEND){ + + if($this->bitBuffer->available() < 16){ + throw new QRCodeDecoderException('structured append: not enough bits left'); + } + // sequence number and parity is added later to the result metadata + // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue + $symbolSequence = $this->bitBuffer->read(8); + $parityData = $this->bitBuffer->read(8); + } + elseif($datamode === Mode::FNC1_FIRST || $datamode === Mode::FNC1_SECOND){ + // We do little with FNC1 except alter the parsed result a bit according to the spec + $fc1InEffect = true; + } + elseif($datamode === Mode::ECI){ + $result .= ECI::decodeSegment($this->bitBuffer, $versionNumber); + } + elseif($datamode === Mode::HANZI){ + $result .= Hanzi::decodeSegment($this->bitBuffer, $versionNumber); + } + else{ + throw new QRCodeDecoderException('invalid data mode'); + } + + } + + return new DecoderResult([ + 'rawBytes' => $this->bitBuffer, + 'data' => $result, + 'version' => $this->version, + 'eccLevel' => $this->eccLevel, + 'maskPattern' => $this->maskPattern, + 'structuredAppendParity' => $parityData, + 'structuredAppendSequence' => $symbolSequence, + ]); + } + + /** + * + */ + private function decodeAlphanumSegment(int $versionNumber, bool $fc1InEffect):string{ + $str = AlphaNum::decodeSegment($this->bitBuffer, $versionNumber); + + // See section 6.4.8.1, 6.4.8.2 + if($fc1InEffect){ // ??? + // We need to massage the result a bit if in an FNC1 mode: + $str = str_replace(chr(0x1d), '%', $str); + $str = str_replace('%%', '%', $str); + } + + return $str; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php new file mode 100644 index 000000000..02b4d7931 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php @@ -0,0 +1,99 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version}; +use chillerlan\QRCode\Data\QRMatrix; +use function property_exists; + +/** + * Encapsulates the result of decoding a matrix of bits. This typically + * applies to 2D barcode formats. For now, it contains the raw bytes obtained + * as well as a String interpretation of those bytes, if applicable. + * + * @property \chillerlan\QRCode\Common\BitBuffer $rawBytes + * @property string $data + * @property \chillerlan\QRCode\Common\Version $version + * @property \chillerlan\QRCode\Common\EccLevel $eccLevel + * @property \chillerlan\QRCode\Common\MaskPattern $maskPattern + * @property int $structuredAppendParity + * @property int $structuredAppendSequence + */ +final class DecoderResult{ + + private BitBuffer $rawBytes; + private Version $version; + private EccLevel $eccLevel; + private MaskPattern $maskPattern; + private string $data = ''; + private int $structuredAppendParity = -1; + private int $structuredAppendSequence = -1; + + /** + * DecoderResult constructor. + */ + public function __construct(iterable $properties = null){ + + if(!empty($properties)){ + + foreach($properties as $property => $value){ + + if(!property_exists($this, $property)){ + continue; + } + + $this->{$property} = $value; + } + + } + + } + + /** + * @return mixed|null + */ + public function __get(string $property){ + + if(property_exists($this, $property)){ + return $this->{$property}; + } + + return null; + } + + /** + * + */ + public function __toString():string{ + return $this->data; + } + + /** + * + */ + public function hasStructuredAppend():bool{ + return $this->structuredAppendParity >= 0 && $this->structuredAppendSequence >= 0; + } + + /** + * Returns a QRMatrix instance with the settings and data of the reader result + */ + public function getQRMatrix():QRMatrix{ + return (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->writeCodewords($this->rawBytes) + ->setFormatInfo($this->maskPattern) + ->mask($this->maskPattern) + ; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php new file mode 100644 index 000000000..11157afc1 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php @@ -0,0 +1,20 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDecoderException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php b/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php new file mode 100644 index 000000000..2bd539aad --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php @@ -0,0 +1,313 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Decoder; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version}; +use function array_fill, array_reverse, count; + +/** + * Implements Reed-Solomon decoding + * + * The algorithm will not be explained here, but the following references were helpful + * in creating this implementation: + * + * - Bruce Maggs "Decoding Reed-Solomon Codes" (see discussion of Forney's Formula) + * http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps + * - J.I. Hall. "Chapter 5. Generalized Reed-Solomon Codes" (see discussion of Euclidean algorithm) + * https://users.math.msu.edu/users/halljo/classes/codenotes/GRS.pdf + * + * Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation. + * + * @author Sean Owen + * @author William Rucklidge + * @author sanfordsquires + */ +final class ReedSolomonDecoder{ + + private Version $version; + private EccLevel $eccLevel; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + + /** + * Error-correct and copy data blocks together into a stream of bytes + */ + public function decode(array $rawCodewords):BitBuffer{ + $dataBlocks = $this->deinterleaveRawBytes($rawCodewords); + $dataBytes = []; + + foreach($dataBlocks as [$numDataCodewords, $codewordBytes]){ + $corrected = $this->correctErrors($codewordBytes, $numDataCodewords); + + for($i = 0; $i < $numDataCodewords; $i++){ + $dataBytes[] = $corrected[$i]; + } + } + + return new BitBuffer($dataBytes); + } + + /** + * When QR Codes use multiple data blocks, they are actually interleaved. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function deinterleaveRawBytes(array $rawCodewords):array{ + // Figure out the number and size of data blocks used by this version and + // error correction level + [$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel); + + // Now establish DataBlocks of the appropriate size and number of data codewords + $result = [];//new DataBlock[$totalBlocks]; + $numResultBlocks = 0; + + foreach($eccBlocks as [$numEccBlocks, $eccPerBlock]){ + for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){ + $result[$numResultBlocks] = [$eccPerBlock, array_fill(0, ($numEccCodewords + $eccPerBlock), 0)]; + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 more byte. Figure out where these start. + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $shorterBlocksTotalCodewords = count($result[0][1]); + $longerBlocksStartAt = (count($result) - 1); + + while($longerBlocksStartAt >= 0){ + $numCodewords = count($result[$longerBlocksStartAt][1]); + + if($numCodewords == $shorterBlocksTotalCodewords){ + break; + } + + $longerBlocksStartAt--; + } + + $longerBlocksStartAt++; + + $shorterBlocksNumDataCodewords = ($shorterBlocksTotalCodewords - $numEccCodewords); + // The last elements of result may be 1 element longer; + // first fill out as many elements as all of them have + $rawCodewordsOffset = 0; + + for($i = 0; $i < $shorterBlocksNumDataCodewords; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $result[$j][1][$i] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // Fill out the last data block in the longer ones + for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){ + $result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++]; + } + + // Now add in error correction blocks + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $max = count($result[0][1]); + + for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $iOffset = ($j < $longerBlocksStartAt) ? $i : ($i + 1); + $result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code + return $result; + } + + /** + * Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction. + */ + private function correctErrors(array $codewordBytes, int $numDataCodewords):array{ + // First read into an array of ints + $codewordsInts = []; + + foreach($codewordBytes as $codewordByte){ + $codewordsInts[] = ($codewordByte & 0xFF); + } + + $decoded = $this->decodeWords($codewordsInts, (count($codewordBytes) - $numDataCodewords)); + + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for($i = 0; $i < $numDataCodewords; $i++){ + $codewordBytes[$i] = $decoded[$i]; + } + + return $codewordBytes; + } + + /** + * Decodes given set of received codewords, which include both data and error-correction + * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, + * in the input. + * + * @param array $received data and error-correction codewords + * @param int $numEccCodewords number of error-correction codewords available + * + * @return int[] + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if decoding fails for any reason + */ + private function decodeWords(array $received, int $numEccCodewords):array{ + $poly = new GenericGFPoly($received); + $syndromeCoefficients = []; + $error = false; + + for($i = 0; $i < $numEccCodewords; $i++){ + $syndromeCoefficients[$i] = $poly->evaluateAt(GF256::exp($i)); + + if($syndromeCoefficients[$i] !== 0){ + $error = true; + } + } + + if(!$error){ + return $received; + } + + [$sigma, $omega] = $this->runEuclideanAlgorithm( + GF256::buildMonomial($numEccCodewords, 1), + new GenericGFPoly(array_reverse($syndromeCoefficients)), + $numEccCodewords + ); + + $errorLocations = $this->findErrorLocations($sigma); + $errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations); + $errorLocationsCount = count($errorLocations); + $receivedCount = count($received); + + for($i = 0; $i < $errorLocationsCount; $i++){ + $position = ($receivedCount - 1 - GF256::log($errorLocations[$i])); + + if($position < 0){ + throw new QRCodeDecoderException('Bad error location'); + } + + $received[$position] ^= $errorMagnitudes[$i]; + } + + return $received; + } + + /** + * @return \chillerlan\QRCode\Common\GenericGFPoly[] [sigma, omega] + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function runEuclideanAlgorithm(GenericGFPoly $a, GenericGFPoly $b, int $z):array{ + // Assume a's degree is >= b's + if($a->getDegree() < $b->getDegree()){ + $temp = $a; + $a = $b; + $b = $temp; + } + + $rLast = $a; + $r = $b; + $tLast = new GenericGFPoly([0]); + $t = new GenericGFPoly([1]); + + // Run Euclidean algorithm until r's degree is less than z/2 + while((2 * $r->getDegree()) >= $z){ + $rLastLast = $rLast; + $tLastLast = $tLast; + $rLast = $r; + $tLast = $t; + + // Divide rLastLast by rLast, with quotient in q and remainder in r + [$q, $r] = $rLastLast->divide($rLast); + + $t = $q->multiply($tLast)->addOrSubtract($tLastLast); + + if($r->getDegree() >= $rLast->getDegree()){ + throw new QRCodeDecoderException('Division algorithm failed to reduce polynomial?'); + } + } + + $sigmaTildeAtZero = $t->getCoefficient(0); + + if($sigmaTildeAtZero === 0){ + throw new QRCodeDecoderException('sigmaTilde(0) was zero'); + } + + $inverse = GF256::inverse($sigmaTildeAtZero); + + return [$t->multiplyInt($inverse), $r->multiplyInt($inverse)]; + } + + /** + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function findErrorLocations(GenericGFPoly $errorLocator):array{ + // This is a direct application of Chien's search + $numErrors = $errorLocator->getDegree(); + + if($numErrors === 1){ // shortcut + return [$errorLocator->getCoefficient(1)]; + } + + $result = array_fill(0, $numErrors, 0); + $e = 0; + + for($i = 1; $i < 256 && $e < $numErrors; $i++){ + if($errorLocator->evaluateAt($i) === 0){ + $result[$e] = GF256::inverse($i); + $e++; + } + } + + if($e !== $numErrors){ + throw new QRCodeDecoderException('Error locator degree does not match number of roots'); + } + + return $result; + } + + /** + * + */ + private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{ + // This is directly applying Forney's Formula + $s = count($errorLocations); + $result = []; + + for($i = 0; $i < $s; $i++){ + $xiInverse = GF256::inverse($errorLocations[$i]); + $denominator = 1; + + for($j = 0; $j < $s; $j++){ + if($i !== $j){ +# $denominator = GF256::multiply($denominator, GF256::addOrSubtract(1, GF256::multiply($errorLocations[$j], $xiInverse))); + // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. + // Below is a funny-looking workaround from Steven Parkes + $term = GF256::multiply($errorLocations[$j], $xiInverse); + $denominator = GF256::multiply($denominator, ((($term & 0x1) === 0) ? ($term | 1) : ($term & ~1))); + } + } + + $result[$i] = GF256::multiply($errorEvaluator->evaluateAt($xiInverse), GF256::inverse($denominator)); + } + + return $result; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php new file mode 100644 index 000000000..72feafdfa --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php @@ -0,0 +1,34 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +/** + * Encapsulates an alignment pattern, which are the smaller square patterns found in + * all but the simplest QR Codes. + * + * @author Sean Owen + */ +final class AlignmentPattern extends ResultPoint{ + + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new FinderPattern containing an average of the two. + */ + public function combineEstimate(float $i, float $j, float $newModuleSize):self{ + return new self( + (($this->x + $j) / 2.0), + (($this->y + $i) / 2.0), + (($this->estimatedModuleSize + $newModuleSize) / 2.0) + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php new file mode 100644 index 000000000..ca62c6f36 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php @@ -0,0 +1,283 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Decoder\BitMatrix; +use function abs, count; + +/** + * This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder + * patterns but are smaller and appear at regular intervals throughout the image. + * + * At the moment this only looks for the bottom-right alignment pattern. + * + * This is mostly a simplified copy of FinderPatternFinder. It is copied, + * pasted and stripped down here for maximum performance but does unfortunately duplicate + * some code. + * + * This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + * @author Sean Owen + */ +final class AlignmentPatternFinder{ + + private BitMatrix $matrix; + private float $moduleSize; + /** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */ + private array $possibleCenters; + + /** + * Creates a finder that will look in a portion of the whole image. + * + * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image to search + * @param float $moduleSize estimated module size so far + */ + public function __construct(BitMatrix $matrix, float $moduleSize){ + $this->matrix = $matrix; + $this->moduleSize = $moduleSize; + $this->possibleCenters = []; + } + + /** + * This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since + * it's pretty performance-critical and so is written to be fast foremost. + * + * @param int $startX left column from which to start searching + * @param int $startY top row from which to start searching + * @param int $width width of region to search + * @param int $height height of region to search + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null + */ + public function find(int $startX, int $startY, int $width, int $height):?AlignmentPattern{ + $maxJ = ($startX + $width); + $middleI = ($startY + ($height / 2)); + $stateCount = []; + + // We are looking for black/white/black modules in 1:1:1 ratio; + // this tracks the number of black/white/black modules seen so far + for($iGen = 0; $iGen < $height; $iGen++){ + // Search from middle outwards + $i = (int)($middleI + ((($iGen & 0x01) === 0) ? ($iGen + 1) / 2 : -(($iGen + 1) / 2))); + $stateCount[0] = 0; + $stateCount[1] = 0; + $stateCount[2] = 0; + $j = $startX; + // Burn off leading white pixels before anything else; if we start in the middle of + // a white run, it doesn't make sense to count its length, since we don't know if the + // white run continued to the left of the start point + while($j < $maxJ && !$this->matrix->check($j, $i)){ + $j++; + } + + $currentState = 0; + + while($j < $maxJ){ + + if($this->matrix->check($j, $i)){ + // Black pixel + if($currentState === 1){ // Counting black pixels + $stateCount[$currentState]++; + } + // Counting white pixels + else{ + // A winner? + if($currentState === 2){ + // Yes + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $j); + + if($confirmed !== null){ + return $confirmed; + } + } + + $stateCount[0] = $stateCount[2]; + $stateCount[1] = 1; + $stateCount[2] = 0; + $currentState = 1; + } + else{ + $stateCount[++$currentState]++; + } + } + } + // White pixel + else{ + // Counting black pixels + if($currentState === 1){ + $currentState++; + } + + $stateCount[$currentState]++; + } + + $j++; + } + + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ); + + if($confirmed !== null){ + return $confirmed; + } + } + + } + + // Hmm, nothing we saw was observed and confirmed twice. If we had + // any guess at all, return it. + if(count($this->possibleCenters)){ + return $this->possibleCenters[0]; + } + + return null; + } + + /** + * @param int[] $stateCount count of black/white/black pixels just read + * + * @return bool true if the proportions of the counts is close enough to the 1/1/1 ratios + * used by alignment patterns to be considered a match + */ + private function foundPatternCross(array $stateCount):bool{ + $maxVariance = ($this->moduleSize / 2.0); + + for($i = 0; $i < 3; $i++){ + if(abs($this->moduleSize - $stateCount[$i]) >= $maxVariance){ + return false; + } + } + + return true; + } + + /** + * This is called when a horizontal scan finds a possible alignment pattern. It will + * cross-check with a vertical scan, and if successful, will see if this pattern had been + * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have + * found the alignment pattern. + * + * @param int[] $stateCount reading state module counts from horizontal scan + * @param int $i row where alignment pattern may be found + * @param int $j end of possible alignment pattern in row + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if we have found the same pattern twice, or null if not + */ + private function handlePossibleCenter(array $stateCount, int $i, int $j):?AlignmentPattern{ + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2]); + $centerJ = $this->centerFromEnd($stateCount, $j); + $centerI = $this->crossCheckVertical($i, (int)$centerJ, (2 * $stateCount[1]), $stateCountTotal); + + if($centerI !== null){ + $estimatedModuleSize = (($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0); + + foreach($this->possibleCenters as $center){ + // Look for about the same center and module size: + if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){ + return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize); + } + } + + // Hadn't found this before; save it + $point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize); + $this->possibleCenters[] = $point; + } + + return null; + } + + /** + * Given a count of black/white/black pixels just seen and an end position, + * figures the location of the center of this black/white/black run. + * + * @param int[] $stateCount + * @param int $end + * + * @return float + */ + private function centerFromEnd(array $stateCount, int $end):float{ + return (float)(($end - $stateCount[2]) - $stateCount[1] / 2); + } + + /** + * After a horizontal scan finds a potential alignment pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * alignment pattern to see if the same proportion is detected. + * + * @param int $startI row where an alignment pattern was detected + * @param int $centerJ center of the section that appears to cross an alignment pattern + * @param int $maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @param int $originalStateCountTotal + * + * @return float|null vertical center of alignment pattern, or null if not found + */ + private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{ + $maxI = $this->matrix->getSize(); + $stateCount = []; + $stateCount[0] = 0; + $stateCount[1] = 0; + $stateCount[2] = 0; + + // Start counting up from center + $i = $startI; + while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i--; + } + // If already too many modules in this state or ran off the edge: + if($i < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $i--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + // Now also count down from center + $i = ($startI + 1); + while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i++; + } + + if($i == $maxI || $stateCount[1] > $maxCount){ + return null; + } + + while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount){ + $stateCount[2]++; + $i++; + } + + if($stateCount[2] > $maxCount){ + return null; + } + + if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $i); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php b/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php new file mode 100644 index 000000000..e43798b9f --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/Detector.php @@ -0,0 +1,350 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Common\{LuminanceSourceInterface, Version}; +use chillerlan\QRCode\Decoder\{Binarizer, BitMatrix}; +use function abs, intdiv, is_nan, max, min, round; +use const NAN; + +/** + * Encapsulates logic that can detect a QR Code in an image, even if the QR Code + * is rotated or skewed, or partially obscured. + * + * @author Sean Owen + */ +final class Detector{ + + private BitMatrix $matrix; + + /** + * Detector constructor. + */ + public function __construct(LuminanceSourceInterface $source){ + $this->matrix = (new Binarizer($source))->getBlackMatrix(); + } + + /** + * Detects a QR Code in an image. + */ + public function detect():BitMatrix{ + [$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find(); + + $moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft); + $dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize); + $provisionalVersion = new Version(intdiv(($dimension - 17), 4)); + $alignmentPattern = null; + + // Anything above version 1 has an alignment pattern + if(!empty($provisionalVersion->getAlignmentPattern())){ + // Guess where a "bottom right" finder pattern would have been + $bottomRightX = ($topRight->getX() - $topLeft->getX() + $bottomLeft->getX()); + $bottomRightY = ($topRight->getY() - $topLeft->getY() + $bottomLeft->getY()); + + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + $correctionToTopLeft = (1.0 - 3.0 / (float)($provisionalVersion->getDimension() - 7)); + $estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX())); + $estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY())); + + // Kind of arbitrary -- expand search radius before giving up + for($i = 4; $i <= 16; $i <<= 1){//?????????? + $alignmentPattern = $this->findAlignmentInRegion($moduleSize, $estAlignmentX, $estAlignmentY, (float)$i); + + if($alignmentPattern !== null){ + break; + } + } + // If we didn't find alignment pattern... well try anyway without it + } + + $transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern); + + return (new GridSampler)->sampleGrid($this->matrix, $dimension, $transform); + } + + /** + * Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns. + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException + */ + private function calculateModuleSize(FinderPattern $topLeft, FinderPattern $topRight, FinderPattern $bottomLeft):float{ + // Take the average + $moduleSize = (( + $this->calculateModuleSizeOneWay($topLeft, $topRight) + + $this->calculateModuleSizeOneWay($topLeft, $bottomLeft) + ) / 2.0); + + if($moduleSize < 1.0){ + throw new QRCodeDetectorException('module size < 1.0'); + } + + return $moduleSize; + } + + /** + * Estimates module size based on two finder patterns -- it uses + * #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int) to figure the + * width of each, measuring along the axis between their centers. + */ + private function calculateModuleSizeOneWay(FinderPattern $a, FinderPattern $b):float{ + + $moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($a->getX(), $a->getY(), $b->getX(), $b->getY()); + $moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays($b->getX(), $b->getY(), $a->getX(), $a->getY()); + + if(is_nan($moduleSizeEst1)){ + return ($moduleSizeEst2 / 7.0); + } + + if(is_nan($moduleSizeEst2)){ + return ($moduleSizeEst1 / 7.0); + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (($moduleSizeEst1 + $moduleSizeEst2) / 14.0); + } + + /** + * See #sizeOfBlackWhiteBlackRun(int, int, int, int); computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another po$(another finder pattern center), and in the opposite direction too. + * + * @noinspection DuplicatedCode + */ + private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{ + $result = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY); + $dimension = $this->matrix->getSize(); + // Now count other way -- don't run off image though of course + $scale = 1.0; + $otherToX = ($fromX - ($toX - $fromX)); + + if($otherToX < 0){ + $scale = ($fromX / ($fromX - $otherToX)); + $otherToX = 0; + } + elseif($otherToX >= $dimension){ + $scale = (($dimension - 1 - $fromX) / ($otherToX - $fromX)); + $otherToX = ($dimension - 1); + } + + $otherToY = (int)($fromY - ($toY - $fromY) * $scale); + $scale = 1.0; + + if($otherToY < 0){ + $scale = ($fromY / ($fromY - $otherToY)); + $otherToY = 0; + } + elseif($otherToY >= $dimension){ + $scale = (($dimension - 1 - $fromY) / ($otherToY - $fromY)); + $otherToY = ($dimension - 1); + } + + $otherToX = (int)($fromX + ($otherToX - $fromX) * $scale); + $result += $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, $otherToX, $otherToY); + + // Middle pixel is double-counted this way; subtract 1 + return ($result - 1.0); + } + + /** + * This method traces a line from a po$in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point. + * + * This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated. + */ + private function sizeOfBlackWhiteBlackRun(int $fromX, int $fromY, int $toX, int $toY):float{ + // Mild variant of Bresenham's algorithm; + // @see https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + $steep = abs($toY - $fromY) > abs($toX - $fromX); + + if($steep){ + $temp = $fromX; + $fromX = $fromY; + $fromY = $temp; + $temp = $toX; + $toX = $toY; + $toY = $temp; + } + + $dx = abs($toX - $fromX); + $dy = abs($toY - $fromY); + $error = (-$dx / 2); + $xstep = (($fromX < $toX) ? 1 : -1); + $ystep = (($fromY < $toY) ? 1 : -1); + + // In black pixels, looking for white, first or second time. + $state = 0; + // Loop up until x == toX, but not beyond + $xLimit = ($toX + $xstep); + + for($x = $fromX, $y = $fromY; $x !== $xLimit; $x += $xstep){ + $realX = ($steep) ? $y : $x; + $realY = ($steep) ? $x : $y; + + // Does current pixel mean we have moved white to black or vice versa? + // Scanning black in state 0,2 and white in state 1, so if we find the wrong + // color, advance to next state or end if we are in state 2 already + if(($state === 1) === $this->matrix->check($realX, $realY)){ + + if($state === 2){ + return FinderPattern::distance($x, $y, $fromX, $fromY); + } + + $state++; + } + + $error += $dy; + + if($error > 0){ + + if($y === $toY){ + break; + } + + $y += $ystep; + $error -= $dx; + } + } + + // Found black-white-black; give the benefit of the doubt that the next pixel outside the image + // is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a + // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. + if($state === 2){ + return FinderPattern::distance(($toX + $xstep), $toY, $fromX, $fromY); + } + + // else we didn't find even black-white-black; no estimate is really possible + return NAN; + } + + /** + * Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size. + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException + */ + private function computeDimension(FinderPattern $nw, FinderPattern $ne, FinderPattern $sw, float $size):int{ + $tltrCentersDimension = (int)round($nw->getDistance($ne) / $size); + $tlblCentersDimension = (int)round($nw->getDistance($sw) / $size); + $dimension = (int)((($tltrCentersDimension + $tlblCentersDimension) / 2) + 7); + + switch($dimension % 4){ + case 0: + $dimension++; + break; + // 1? do nothing + case 2: + $dimension--; + break; + case 3: + throw new QRCodeDetectorException('estimated dimension: '.$dimension); + } + + if(($dimension % 4) !== 1){ + throw new QRCodeDetectorException('dimension mod 4 is not 1'); + } + + return $dimension; + } + + /** + * Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. + * + * @param float $overallEstModuleSize estimated module size so far + * @param int $estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param int $estAlignmentY y coordinate of above + * @param float $allowanceFactor number of pixels in all directions to search from the center + * + * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if found, or null otherwise + */ + private function findAlignmentInRegion( + float $overallEstModuleSize, + int $estAlignmentX, + int $estAlignmentY, + float $allowanceFactor + ):?AlignmentPattern{ + // Look for an alignment pattern (3 modules in size) around where it should be + $dimension = $this->matrix->getSize(); + $allowance = (int)($allowanceFactor * $overallEstModuleSize); + $alignmentAreaLeftX = max(0, ($estAlignmentX - $allowance)); + $alignmentAreaRightX = min(($dimension - 1), ($estAlignmentX + $allowance)); + + if(($alignmentAreaRightX - $alignmentAreaLeftX) < ($overallEstModuleSize * 3)){ + return null; + } + + $alignmentAreaTopY = max(0, ($estAlignmentY - $allowance)); + $alignmentAreaBottomY = min(($dimension - 1), ($estAlignmentY + $allowance)); + + if(($alignmentAreaBottomY - $alignmentAreaTopY) < ($overallEstModuleSize * 3)){ + return null; + } + + return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find( + $alignmentAreaLeftX, + $alignmentAreaTopY, + ($alignmentAreaRightX - $alignmentAreaLeftX), + ($alignmentAreaBottomY - $alignmentAreaTopY), + ); + } + + /** + * + */ + private function createTransform( + FinderPattern $nw, + FinderPattern $ne, + FinderPattern $sw, + int $size, + AlignmentPattern $ap = null + ):PerspectiveTransform{ + $dimMinusThree = ($size - 3.5); + + if($ap instanceof AlignmentPattern){ + $bottomRightX = $ap->getX(); + $bottomRightY = $ap->getY(); + $sourceBottomRightX = ($dimMinusThree - 3.0); + $sourceBottomRightY = $sourceBottomRightX; + } + else{ + // Don't have an alignment pattern, just make up the bottom-right point + $bottomRightX = ($ne->getX() - $nw->getX() + $sw->getX()); + $bottomRightY = ($ne->getY() - $nw->getY() + $sw->getY()); + $sourceBottomRightX = $dimMinusThree; + $sourceBottomRightY = $dimMinusThree; + } + + return (new PerspectiveTransform)->quadrilateralToQuadrilateral( + 3.5, + 3.5, + $dimMinusThree, + 3.5, + $sourceBottomRightX, + $sourceBottomRightY, + 3.5, + $dimMinusThree, + $nw->getX(), + $nw->getY(), + $ne->getX(), + $ne->getY(), + $bottomRightX, + $bottomRightY, + $sw->getX(), + $sw->getY() + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php new file mode 100644 index 000000000..1f799e87e --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php @@ -0,0 +1,92 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function sqrt; + +/** + * Encapsulates a finder pattern, which are the three square patterns found in + * the corners of QR Codes. It also encapsulates a count of similar finder patterns, + * as a convenience to the finder's bookkeeping. + * + * @author Sean Owen + */ +final class FinderPattern extends ResultPoint{ + + private int $count; + + /** + * + */ + public function __construct(float $posX, float $posY, float $estimatedModuleSize, int $count = null){ + parent::__construct($posX, $posY, $estimatedModuleSize); + + $this->count = ($count ?? 1); + } + + /** + * + */ + public function getCount():int{ + return $this->count; + } + + /** + * @param \chillerlan\QRCode\Detector\FinderPattern $b second pattern + * + * @return float distance between two points + */ + public function getDistance(FinderPattern $b):float{ + return self::distance($this->x, $this->y, $b->x, $b->y); + } + + /** + * Get square of distance between a and b. + */ + public function getSquaredDistance(FinderPattern $b):float{ + return self::squaredDistance($this->x, $this->y, $b->x, $b->y); + } + + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new FinderPattern containing a weighted average + * based on count. + */ + public function combineEstimate(float $i, float $j, float $newModuleSize):self{ + $combinedCount = ($this->count + 1); + + return new self( + ($this->count * $this->x + $j) / $combinedCount, + ($this->count * $this->y + $i) / $combinedCount, + ($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount, + $combinedCount + ); + } + + /** + * + */ + private static function squaredDistance(float $aX, float $aY, float $bX, float $bY):float{ + $xDiff = ($aX - $bX); + $yDiff = ($aY - $bY); + + return ($xDiff * $xDiff + $yDiff * $yDiff); + } + + /** + * + */ + public static function distance(float $aX, float $aY, float $bX, float $bY):float{ + return sqrt(self::squaredDistance($aX, $aY, $bX, $bY)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php new file mode 100644 index 000000000..755d08c27 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php @@ -0,0 +1,770 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + * + * @phan-file-suppress PhanTypePossiblyInvalidDimOffset + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Decoder\BitMatrix; +use function abs, count, intdiv, usort; +use const PHP_FLOAT_MAX; + +/** + * This class attempts to find finder patterns in a QR Code. Finder patterns are the square + * markers at three corners of a QR Code. + * + * This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + * @author Sean Owen + */ +final class FinderPatternFinder{ + + private const MIN_SKIP = 2; + private const MAX_MODULES = 177; // 1 pixel/module times 3 modules/center + private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients + private BitMatrix $matrix; + /** @var \chillerlan\QRCode\Detector\FinderPattern[] */ + private array $possibleCenters; + private bool $hasSkipped = false; + + /** + * Creates a finder that will search the image for three finder patterns. + * + * @param BitMatrix $matrix image to search + */ + public function __construct(BitMatrix $matrix){ + $this->matrix = $matrix; + $this->possibleCenters = []; + } + + /** + * @return \chillerlan\QRCode\Detector\FinderPattern[] + */ + public function find():array{ + $dimension = $this->matrix->getSize(); + + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. + $iSkip = intdiv((3 * $dimension), (4 * self::MAX_MODULES)); + + if($iSkip < self::MIN_SKIP){ + $iSkip = self::MIN_SKIP; + } + + $done = false; + + for($i = ($iSkip - 1); ($i < $dimension) && !$done; $i += $iSkip){ + // Get a row of black/white values + $stateCount = $this->getCrossCheckStateCount(); + $currentState = 0; + + for($j = 0; $j < $dimension; $j++){ + + // Black pixel + if($this->matrix->check($j, $i)){ + // Counting white pixels + if(($currentState & 1) === 1){ + $currentState++; + } + + $stateCount[$currentState]++; + } + // White pixel + else{ + // Counting black pixels + if(($currentState & 1) === 0){ + // A winner? + if($currentState === 4){ + // Yes + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $j); + + if($confirmed){ + // Start examining every other line. Checking each line turned out to be too + // expensive and didn't improve performance. + $iSkip = 3; + + if($this->hasSkipped){ + $done = $this->haveMultiplyConfirmedCenters(); + } + else{ + $rowSkip = $this->findRowSkip(); + + if($rowSkip > $stateCount[2]){ + // Skip rows between row of lower confirmed center + // and top of presumed third confirmed center + // but back up a bit to get a full chance of detecting + // it, entire width of center of finder pattern + + // Skip by rowSkip, but back off by $stateCount[2] (size of last center + // of pattern we saw) to be conservative, and also back off by iSkip which + // is about to be re-added + $i += ($rowSkip - $stateCount[2] - $iSkip); + $j = ($dimension - 1); + } + } + } + else{ + $stateCount = $this->doShiftCounts2($stateCount); + $currentState = 3; + + continue; + } + // Clear state to start looking again + $currentState = 0; + $stateCount = $this->getCrossCheckStateCount(); + } + // No, shift counts back by two + else{ + $stateCount = $this->doShiftCounts2($stateCount); + $currentState = 3; + } + } + else{ + $stateCount[++$currentState]++; + } + } + // Counting white pixels + else{ + $stateCount[$currentState]++; + } + } + } + + if($this->foundPatternCross($stateCount)){ + $confirmed = $this->handlePossibleCenter($stateCount, $i, $dimension); + + if($confirmed){ + $iSkip = $stateCount[0]; + + if($this->hasSkipped){ + // Found a third one + $done = $this->haveMultiplyConfirmedCenters(); + } + } + } + } + + return $this->orderBestPatterns($this->selectBestPatterns()); + } + + /** + * @return int[] + */ + private function getCrossCheckStateCount():array{ + return [0, 0, 0, 0, 0]; + } + + /** + * @param int[] $stateCount + * + * @return int[] + */ + private function doShiftCounts2(array $stateCount):array{ + $stateCount[0] = $stateCount[2]; + $stateCount[1] = $stateCount[3]; + $stateCount[2] = $stateCount[4]; + $stateCount[3] = 1; + $stateCount[4] = 0; + + return $stateCount; + } + + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + * + * @param int[] $stateCount + */ + private function centerFromEnd(array $stateCount, int $end):float{ + return (float)(($end - $stateCount[4] - $stateCount[3]) - $stateCount[2] / 2); + } + + /** + * @param int[] $stateCount + */ + private function foundPatternCross(array $stateCount):bool{ + // Allow less than 50% variance from 1-1-3-1-1 proportions + return $this->foundPatternVariance($stateCount, 2.0); + } + + /** + * @param int[] $stateCount + */ + private function foundPatternDiagonal(array $stateCount):bool{ + // Allow less than 75% variance from 1-1-3-1-1 proportions + return $this->foundPatternVariance($stateCount, 1.333); + } + + /** + * @param int[] $stateCount count of black/white/black/white/black pixels just read + * + * @return bool true if the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + private function foundPatternVariance(array $stateCount, float $variance):bool{ + $totalModuleSize = 0; + + for($i = 0; $i < 5; $i++){ + $count = $stateCount[$i]; + + if($count === 0){ + return false; + } + + $totalModuleSize += $count; + } + + if($totalModuleSize < 7){ + return false; + } + + $moduleSize = ($totalModuleSize / 7.0); + $maxVariance = ($moduleSize / $variance); + + return + abs($moduleSize - $stateCount[0]) < $maxVariance + && abs($moduleSize - $stateCount[1]) < $maxVariance + && abs(3.0 * $moduleSize - $stateCount[2]) < (3 * $maxVariance) + && abs($moduleSize - $stateCount[3]) < $maxVariance + && abs($moduleSize - $stateCount[4]) < $maxVariance; + } + + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param int $centerI row where a finder pattern was detected + * @param int $centerJ center of the section that appears to cross a finder pattern + * + * @return bool true if proportions are withing expected limits + */ + private function crossCheckDiagonal(int $centerI, int $centerJ):bool{ + $stateCount = $this->getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + $i = 0; + + while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[2]++; + $i++; + } + + if($stateCount[2] === 0){ + return false; + } + + // Continue up, left finding white space + while($centerI >= $i && $centerJ >= $i && !$this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[1]++; + $i++; + } + + if($stateCount[1] === 0){ + return false; + } + + // Continue up, left finding black border + while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){ + $stateCount[0]++; + $i++; + } + + if($stateCount[0] === 0){ + return false; + } + + $dimension = $this->matrix->getSize(); + + // Now also count down, right from center + $i = 1; + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[2]++; + $i++; + } + + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[3]++; + $i++; + } + + if($stateCount[3] === 0){ + return false; + } + + while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){ + $stateCount[4]++; + $i++; + } + + if($stateCount[4] === 0){ + return false; + } + + return $this->foundPatternDiagonal($stateCount); + } + + /** + * After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param int $startI row where a finder pattern was detected + * @param int $centerJ center of the section that appears to cross a finder pattern + * @param int $maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @param int $originalStateCountTotal + * + * @return float|null vertical center of finder pattern, or null if not found + * @noinspection DuplicatedCode + */ + private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{ + $maxI = $this->matrix->getSize(); + $stateCount = $this->getCrossCheckStateCount(); + + // Start counting up from center + $i = $startI; + while($i >= 0 && $this->matrix->check($centerJ, $i)){ + $stateCount[2]++; + $i--; + } + + if($i < 0){ + return null; + } + + while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $i--; + } + + // If already too many modules in this state or ran off the edge: + if($i < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $i--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + // Now also count down from center + $i = ($startI + 1); + while($i < $maxI && $this->matrix->check($centerJ, $i)){ + $stateCount[2]++; + $i++; + } + + if($i === $maxI){ + return null; + } + + while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[3] < $maxCount){ + $stateCount[3]++; + $i++; + } + + if($i === $maxI || $stateCount[3] >= $maxCount){ + return null; + } + + while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[4] < $maxCount){ + $stateCount[4]++; + $i++; + } + + if($stateCount[4] >= $maxCount){ + return null; + } + + // If we found a finder-pattern-like section, but its size is more than 40% different from + // the original, assume it's a false positive + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + + if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $i); + } + + /** + * Like #crossCheckVertical(int, int, int, int), and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross-check and locate the real center of the alignment pattern. + * @noinspection DuplicatedCode + */ + private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{ + $maxJ = $this->matrix->getSize(); + $stateCount = $this->getCrossCheckStateCount(); + + $j = $startJ; + while($j >= 0 && $this->matrix->check($j, $centerI)){ + $stateCount[2]++; + $j--; + } + + if($j < 0){ + return null; + } + + while($j >= 0 && !$this->matrix->check($j, $centerI) && $stateCount[1] <= $maxCount){ + $stateCount[1]++; + $j--; + } + + if($j < 0 || $stateCount[1] > $maxCount){ + return null; + } + + while($j >= 0 && $this->matrix->check($j, $centerI) && $stateCount[0] <= $maxCount){ + $stateCount[0]++; + $j--; + } + + if($stateCount[0] > $maxCount){ + return null; + } + + $j = ($startJ + 1); + while($j < $maxJ && $this->matrix->check($j, $centerI)){ + $stateCount[2]++; + $j++; + } + + if($j === $maxJ){ + return null; + } + + while($j < $maxJ && !$this->matrix->check($j, $centerI) && $stateCount[3] < $maxCount){ + $stateCount[3]++; + $j++; + } + + if($j === $maxJ || $stateCount[3] >= $maxCount){ + return null; + } + + while($j < $maxJ && $this->matrix->check($j, $centerI) && $stateCount[4] < $maxCount){ + $stateCount[4]++; + $j++; + } + + if($stateCount[4] >= $maxCount){ + return null; + } + + // If we found a finder-pattern-like section, but its size is significantly different from + // the original, assume it's a false positive + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + + if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= $originalStateCountTotal){ + return null; + } + + if(!$this->foundPatternCross($stateCount)){ + return null; + } + + return $this->centerFromEnd($stateCount, $j); + } + + /** + * This is called when a horizontal scan finds a possible alignment pattern. It will + * cross-check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew. + * And then we cross-cross-cross check with another diagonal scan. + * + * If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param int[] $stateCount reading state module counts from horizontal scan + * @param int $i row where finder pattern may be found + * @param int $j end of possible finder pattern in row + * + * @return bool if a finder pattern candidate was found this time + */ + private function handlePossibleCenter(array $stateCount, int $i, int $j):bool{ + $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]); + $centerJ = $this->centerFromEnd($stateCount, $j); + $centerI = $this->crossCheckVertical($i, (int)$centerJ, $stateCount[2], $stateCountTotal); + + if($centerI !== null){ + // Re-cross check + $centerJ = $this->crossCheckHorizontal((int)$centerJ, (int)$centerI, $stateCount[2], $stateCountTotal); + if($centerJ !== null && ($this->crossCheckDiagonal((int)$centerI, (int)$centerJ))){ + $estimatedModuleSize = ($stateCountTotal / 7.0); + $found = false; + + // cautious (was in for fool in which $this->possibleCenters is updated) + $count = count($this->possibleCenters); + + for($index = 0; $index < $count; $index++){ + $center = $this->possibleCenters[$index]; + // Look for about the same center and module size: + if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){ + $this->possibleCenters[$index] = $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize); + $found = true; + break; + } + } + + if(!$found){ + $point = new FinderPattern($centerJ, $centerI, $estimatedModuleSize); + $this->possibleCenters[] = $point; + } + + return true; + } + } + + return false; + } + + /** + * @return int number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + private function findRowSkip():int{ + $max = count($this->possibleCenters); + + if($max <= 1){ + return 0; + } + + $firstConfirmedCenter = null; + + foreach($this->possibleCenters as $center){ + + if($center->getCount() >= self::CENTER_QUORUM){ + + if($firstConfirmedCenter === null){ + $firstConfirmedCenter = $center; + } + else{ + // We have two confirmed centers + // How far down can we skip before resuming looking for the next + // pattern? In the worst case, only the difference between the + // difference in the x / y coordinates of the two centers. + // This is the case where you find top left last. + $this->hasSkipped = true; + + return (int)((abs($firstConfirmedCenter->getX() - $center->getX()) - + abs($firstConfirmedCenter->getY() - $center->getY())) / 2); + } + } + } + + return 0; + } + + /** + * @return bool true if we have found at least 3 finder patterns that have been detected + * at least #CENTER_QUORUM times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + private function haveMultiplyConfirmedCenters():bool{ + $confirmedCount = 0; + $totalModuleSize = 0.0; + $max = count($this->possibleCenters); + + foreach($this->possibleCenters as $pattern){ + if($pattern->getCount() >= self::CENTER_QUORUM){ + $confirmedCount++; + $totalModuleSize += $pattern->getEstimatedModuleSize(); + } + } + + if($confirmedCount < 3){ + return false; + } + // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" + // and that we need to keep looking. We detect this by asking if the estimated module sizes + // vary too much. We arbitrarily say that when the total deviation from average exceeds + // 5% of the total module size estimates, it's too much. + $average = ($totalModuleSize / (float)$max); + $totalDeviation = 0.0; + + foreach($this->possibleCenters as $pattern){ + $totalDeviation += abs($pattern->getEstimatedModuleSize() - $average); + } + + return $totalDeviation <= (0.05 * $totalModuleSize); + } + + /** + * @return \chillerlan\QRCode\Detector\FinderPattern[] the 3 best FinderPatterns from our list of candidates. The "best" are + * those that have been detected at least #CENTER_QUORUM times, and whose module + * size differs from the average among those patterns the least + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if 3 such finder patterns do not exist + */ + private function selectBestPatterns():array{ + $startSize = count($this->possibleCenters); + + if($startSize < 3){ + throw new QRCodeDetectorException('could not find enough finder patterns'); + } + + usort( + $this->possibleCenters, + fn(FinderPattern $a, FinderPattern $b) => ($a->getEstimatedModuleSize() <=> $b->getEstimatedModuleSize()) + ); + + $distortion = PHP_FLOAT_MAX; + $bestPatterns = []; + + for($i = 0; $i < ($startSize - 2); $i++){ + $fpi = $this->possibleCenters[$i]; + $minModuleSize = $fpi->getEstimatedModuleSize(); + + for($j = ($i + 1); $j < ($startSize - 1); $j++){ + $fpj = $this->possibleCenters[$j]; + $squares0 = $fpi->getSquaredDistance($fpj); + + for($k = ($j + 1); $k < $startSize; $k++){ + $fpk = $this->possibleCenters[$k]; + $maxModuleSize = $fpk->getEstimatedModuleSize(); + + // module size is not similar + if($maxModuleSize > ($minModuleSize * 1.4)){ + continue; + } + + $a = $squares0; + $b = $fpj->getSquaredDistance($fpk); + $c = $fpi->getSquaredDistance($fpk); + + // sorts ascending - inlined + if($a < $b){ + if($b > $c){ + if($a < $c){ + $temp = $b; + $b = $c; + $c = $temp; + } + else{ + $temp = $a; + $a = $c; + $c = $b; + $b = $temp; + } + } + } + else{ + if($b < $c){ + if($a < $c){ + $temp = $a; + $a = $b; + $b = $temp; + } + else{ + $temp = $a; + $a = $b; + $b = $c; + $c = $temp; + } + } + else{ + $temp = $a; + $a = $c; + $c = $temp; + } + } + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + $d = (abs($c - 2 * $b) + abs($c - 2 * $a)); + + if($d < $distortion){ + $distortion = $d; + $bestPatterns = [$fpi, $fpj, $fpk]; + } + } + } + } + + if($distortion === PHP_FLOAT_MAX){ + throw new QRCodeDetectorException('finder patterns may be too distorted'); + } + + return $bestPatterns; + } + + /** + * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC + * and BC is less than AC, and the angle between BC and BA is less than 180 degrees. + * + * @param \chillerlan\QRCode\Detector\FinderPattern[] $patterns array of three FinderPattern to order + * + * @return \chillerlan\QRCode\Detector\FinderPattern[] + */ + private function orderBestPatterns(array $patterns):array{ + + // Find distances between pattern centers + $zeroOneDistance = $patterns[0]->getDistance($patterns[1]); + $oneTwoDistance = $patterns[1]->getDistance($patterns[2]); + $zeroTwoDistance = $patterns[0]->getDistance($patterns[2]); + + // Assume one closest to other two is B; A and C will just be guesses at first + if($oneTwoDistance >= $zeroOneDistance && $oneTwoDistance >= $zeroTwoDistance){ + [$pointB, $pointA, $pointC] = $patterns; + } + elseif($zeroTwoDistance >= $oneTwoDistance && $zeroTwoDistance >= $zeroOneDistance){ + [$pointA, $pointB, $pointC] = $patterns; + } + else{ + [$pointA, $pointC, $pointB] = $patterns; + } + + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative, then we've got it flipped around and + // should swap A and C. + if($this->crossProductZ($pointA, $pointB, $pointC) < 0.0){ + $temp = $pointA; + $pointA = $pointC; + $pointC = $temp; + } + + return [$pointA, $pointB, $pointC]; + } + + /** + * Returns the z component of the cross product between vectors BC and BA. + */ + private function crossProductZ(FinderPattern $pointA, FinderPattern $pointB, FinderPattern $pointC):float{ + $bX = $pointB->getX(); + $bY = $pointB->getY(); + + return ((($pointC->getX() - $bX) * ($pointA->getY() - $bY)) - (($pointC->getY() - $bY) * ($pointA->getX() - $bX))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php b/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php new file mode 100644 index 000000000..0d915e3e0 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php @@ -0,0 +1,181 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\QRCode\Decoder\BitMatrix; +use function array_fill, count, intdiv, sprintf; + +/** + * Implementations of this class can, given locations of finder patterns for a QR code in an + * image, sample the right points in the image to reconstruct the QR code, accounting for + * perspective distortion. It is abstracted since it is relatively expensive and should be allowed + * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced + * Imaging library, but which may not be available in other environments such as J2ME, and vice + * versa. + * + * The implementation used can be controlled by calling #setGridSampler(GridSampler) + * with an instance of a class which implements this interface. + * + * @author Sean Owen + */ +final class GridSampler{ + + private array $points; + + /** + * Checks a set of points that have been transformed to sample points on an image against + * the image's dimensions to see if the point are even within the image. + * + * This method will actually "nudge" the endpoints back onto the image if they are found to be + * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder + * patterns in an image where the QR Code runs all the way to the image border. + * + * For efficiency, the method will check points from either end of the line until one is found + * to be within the image. Because the set of points are assumed to be linear, this is valid. + * + * @param int $dimension matrix width/height + * + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries + */ + private function checkAndNudgePoints(int $dimension):void{ + $nudged = true; + $max = count($this->points); + + // Check and nudge points from start until we see some that are OK: + for($offset = 0; $offset < $max && $nudged; $offset += 2){ + $x = (int)$this->points[$offset]; + $y = (int)$this->points[($offset + 1)]; + + if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){ + throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 1, x: %s, y: %s, d: %s', $x, $y, $dimension)); + } + + $nudged = false; + + if($x === -1){ + $this->points[$offset] = 0.0; + $nudged = true; + } + elseif($x === $dimension){ + $this->points[$offset] = ($dimension - 1); + $nudged = true; + } + + if($y === -1){ + $this->points[($offset + 1)] = 0.0; + $nudged = true; + } + elseif($y === $dimension){ + $this->points[($offset + 1)] = ($dimension - 1); + $nudged = true; + } + + } + + // Check and nudge points from end: + $nudged = true; + + for($offset = ($max - 2); $offset >= 0 && $nudged; $offset -= 2){ + $x = (int)$this->points[$offset]; + $y = (int)$this->points[($offset + 1)]; + + if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){ + throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 2, x: %s, y: %s, d: %s', $x, $y, $dimension)); + } + + $nudged = false; + + if($x === -1){ + $this->points[$offset] = 0.0; + $nudged = true; + } + elseif($x === $dimension){ + $this->points[$offset] = ($dimension - 1); + $nudged = true; + } + + if($y === -1){ + $this->points[($offset + 1)] = 0.0; + $nudged = true; + } + elseif($y === $dimension){ + $this->points[($offset + 1)] = ($dimension - 1); + $nudged = true; + } + + } + + } + + /** + * Samples an image for a rectangular matrix of bits of the given dimension. The sampling + * transformation is determined by the coordinates of 4 points, in the original and transformed + * image space. + * + * @return \chillerlan\QRCode\Decoder\BitMatrix representing a grid of points sampled from the image within a region + * defined by the "from" parameters + * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if image can't be sampled, for example, if the transformation defined + * by the given points is invalid or results in sampling outside the image boundaries + */ + public function sampleGrid(BitMatrix $matrix, int $dimension, PerspectiveTransform $transform):BitMatrix{ + + if($dimension <= 0){ + throw new QRCodeDetectorException('invalid matrix size'); + } + + $bits = new BitMatrix($dimension); + $this->points = array_fill(0, (2 * $dimension), 0.0); + + for($y = 0; $y < $dimension; $y++){ + $max = count($this->points); + $iValue = ($y + 0.5); + + for($x = 0; $x < $max; $x += 2){ + $this->points[$x] = (($x / 2) + 0.5); + $this->points[($x + 1)] = $iValue; + } + // phpcs:ignore + [$this->points, ] = $transform->transformPoints($this->points); + // Quick check to see if points transformed to something inside the image; + // sufficient to check the endpoints + $this->checkAndNudgePoints($matrix->getSize()); + + // no need to try/catch as QRMatrix::set() will silently discard out of bounds values +# try{ + for($x = 0; $x < $max; $x += 2){ + // Black(-ish) pixel + $bits->set( + intdiv($x, 2), + $y, + $matrix->check((int)$this->points[$x], (int)$this->points[($x + 1)]), + QRMatrix::M_DATA + ); + } +# } +# catch(\Throwable $aioobe){//ArrayIndexOutOfBoundsException + // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // transform gets "twisted" such that it maps a straight line of points to a set of points + // whose endpoints are in bounds, but others are not. There is probably some mathematical + // way to detect this about the transformation that I don't know yet. + // This results in an ugly runtime exception despite our clever checks above -- can't have + // that. We could check each point's coordinates but that feels duplicative. We settle for + // catching and wrapping ArrayIndexOutOfBoundsException. +# throw new QRCodeDetectorException('ArrayIndexOutOfBoundsException'); +# } + + } + + return $bits; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php b/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php new file mode 100644 index 000000000..4f1f6547a --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php @@ -0,0 +1,182 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function count; + +/** + * This class implements a perspective transform in two dimensions. Given four source and four + * destination points, it will compute the transformation implied between them. The code is based + * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56. + * + * @author Sean Owen + */ +final class PerspectiveTransform{ + + private float $a11; + private float $a12; + private float $a13; + private float $a21; + private float $a22; + private float $a23; + private float $a31; + private float $a32; + private float $a33; + + /** + * + */ + private function set( + float $a11, float $a21, float $a31, + float $a12, float $a22, float $a32, + float $a13, float $a23, float $a33 + ):self{ + $this->a11 = $a11; + $this->a12 = $a12; + $this->a13 = $a13; + $this->a21 = $a21; + $this->a22 = $a22; + $this->a23 = $a23; + $this->a31 = $a31; + $this->a32 = $a32; + $this->a33 = $a33; + + return $this; + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function quadrilateralToQuadrilateral( + float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3, + float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p + ):self{ + return (new self) + ->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p) + ->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)); + } + + /** + * + */ + private function quadrilateralToSquare( + float $x0, float $y0, float $x1, float $y1, + float $x2, float $y2, float $x3, float $y3 + ):self{ + // Here, the adjoint serves as the inverse: + return $this + ->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) + ->buildAdjoint(); + } + + /** + * + */ + private function buildAdjoint():self{ + // Adjoint is the transpose of the cofactor matrix: + return $this->set( + ($this->a22 * $this->a33 - $this->a23 * $this->a32), + ($this->a23 * $this->a31 - $this->a21 * $this->a33), + ($this->a21 * $this->a32 - $this->a22 * $this->a31), + ($this->a13 * $this->a32 - $this->a12 * $this->a33), + ($this->a11 * $this->a33 - $this->a13 * $this->a31), + ($this->a12 * $this->a31 - $this->a11 * $this->a32), + ($this->a12 * $this->a23 - $this->a13 * $this->a22), + ($this->a13 * $this->a21 - $this->a11 * $this->a23), + ($this->a11 * $this->a22 - $this->a12 * $this->a21) + ); + } + + /** + * + */ + private function squareToQuadrilateral( + float $x0, float $y0, float $x1, float $y1, + float $x2, float $y2, float $x3, float $y3 + ):self{ + $dx3 = ($x0 - $x1 + $x2 - $x3); + $dy3 = ($y0 - $y1 + $y2 - $y3); + + if($dx3 === 0.0 && $dy3 === 0.0){ + // Affine + return $this->set(($x1 - $x0), ($x2 - $x1), $x0, ($y1 - $y0), ($y2 - $y1), $y0, 0.0, 0.0, 1.0); + } + + $dx1 = ($x1 - $x2); + $dx2 = ($x3 - $x2); + $dy1 = ($y1 - $y2); + $dy2 = ($y3 - $y2); + $denominator = ($dx1 * $dy2 - $dx2 * $dy1); + $a13 = (($dx3 * $dy2 - $dx2 * $dy3) / $denominator); + $a23 = (($dx1 * $dy3 - $dx3 * $dy1) / $denominator); + + return $this->set( + ($x1 - $x0 + $a13 * $x1), + ($x3 - $x0 + $a23 * $x3), + $x0, + ($y1 - $y0 + $a13 * $y1), + ($y3 - $y0 + $a23 * $y3), + $y0, + $a13, + $a23, + 1.0 + ); + } + + /** + * + */ + private function times(PerspectiveTransform $other):self{ + return $this->set( + ($this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13), + ($this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23), + ($this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33), + ($this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13), + ($this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23), + ($this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33), + ($this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13), + ($this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23), + ($this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33) + ); + } + + /** + * @return array[] [$xValues, $yValues] + */ + public function transformPoints(array $xValues, array $yValues = null):array{ + $max = count($xValues); + + if($yValues !== null){ // unused + + for($i = 0; $i < $max; $i++){ + $x = $xValues[$i]; + $y = $yValues[$i]; + $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33); + $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator); + $yValues[$i] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator); + } + + return [$xValues, $yValues]; + } + + for($i = 0; $i < $max; $i += 2){ + $x = $xValues[$i]; + $y = $xValues[($i + 1)]; + $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33); + $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator); + $xValues[($i + 1)] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator); + } + + return [$xValues, []]; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php b/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php new file mode 100644 index 000000000..2444e193c --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php @@ -0,0 +1,20 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Detector; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeDetectorException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php b/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php new file mode 100644 index 000000000..92997a746 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php @@ -0,0 +1,73 @@ + + * @copyright 2021 Smiley + * @license Apache-2.0 + */ + +namespace chillerlan\QRCode\Detector; + +use function abs; + +/** + * Encapsulates a point of interest in an image containing a barcode. Typically, this + * would be the location of a finder pattern or the corner of the barcode, for example. + * + * @author Sean Owen + */ +abstract class ResultPoint{ + + protected float $x; + protected float $y; + protected float $estimatedModuleSize; + + /** + * + */ + public function __construct(float $x, float $y, float $estimatedModuleSize){ + $this->x = $x; + $this->y = $y; + $this->estimatedModuleSize = $estimatedModuleSize; + } + + /** + * + */ + public function getX():float{ + return $this->x; + } + + /** + * + */ + public function getY():float{ + return $this->y; + } + + /** + * + */ + public function getEstimatedModuleSize():float{ + return $this->estimatedModuleSize; + } + + /** + * Determines if this finder pattern "about equals" a finder pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size. + */ + public function aboutEquals(float $moduleSize, float $i, float $j):bool{ + + if(abs($i - $this->y) <= $moduleSize && abs($j - $this->x) <= $moduleSize){ + $moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize); + + return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize; + } + + return false; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php new file mode 100644 index 000000000..bf30f1bb0 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\QRCodeException; + +/** + * An exception container + */ +final class QRCodeOutputException extends QRCodeException{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php new file mode 100644 index 000000000..a04f20d5b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QREps.php @@ -0,0 +1,173 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_values, count, date, implode, is_array, is_numeric, max, min, round, sprintf; + +/** + * Encapsulated Postscript (EPS) output + * + * @see https://github.com/t0k4rt/phpqrcode/blob/bb29e6eb77e0a2a85bb0eb62725e0adc11ff5a90/qrvect.php#L52-L137 + * @see https://web.archive.org/web/20170818010030/http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/postscript/pdfs/5002.EPSF_Spec.pdf + * @see https://web.archive.org/web/20210419003859/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf + * @see https://github.com/chillerlan/php-qrcode/discussions/148 + */ +class QREps extends QROutputAbstract{ + + public const MIME_TYPE = 'application/postscript'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + // clamp value and convert from int 0-255 to float 0-1 RGB/CMYK range + $values[] = round((max(0, min(255, intval($val))) / 255), 6); + } + + return $this->formatColor($values); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return $this->formatColor(($isDark) ? [0.0, 0.0, 0.0] : [1.0, 1.0, 1.0]); + } + + /** + * Set the color format string + * + * 4 values in the color array will be interpreted as CMYK, 3 as RGB + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function formatColor(array $values):string{ + $count = count($values); + + if($count < 3){ + throw new QRCodeOutputException('invalid color value'); + } + + $format = ($count === 4) + // CMYK + ? '%f %f %f %f C' + // RGB + :'%f %f %f R'; + + return sprintf($format, ...$values); + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + [$width, $height] = $this->getOutputDimensions(); + + $eps = [ + // main header + '%!PS-Adobe-3.0 EPSF-3.0', + '%%Creator: php-qrcode (https://github.com/chillerlan/php-qrcode)', + '%%Title: QR Code', + sprintf('%%%%CreationDate: %1$s', date('c')), + '%%DocumentData: Clean7Bit', + '%%LanguageLevel: 3', + sprintf('%%%%BoundingBox: 0 0 %s %s', $width, $height), + '%%EndComments', + // function definitions + '%%BeginProlog', + '/F { rectfill } def', + '/R { setrgbcolor } def', + '/C { setcmykcolor } def', + '%%EndProlog', + ]; + + if($this::moduleValueIsValid($this->options->bgColor)){ + $eps[] = $this->prepareModuleValue($this->options->bgColor); + $eps[] = sprintf('0 0 %s %s F', $width, $height); + } + + // create the path elements + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + + foreach($paths as $M_TYPE => $path){ + + if(empty($path)){ + continue; + } + + $eps[] = $this->getModuleValue($M_TYPE); + $eps[] = implode("\n", $path); + } + + // end file + $eps[] = '%%EOF'; + + $data = implode("\n", $eps); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * Returns a path segment for a single module + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + $outputX = ($x * $this->scale); + // Actual size - one block = Topmost y pos. + $top = ($this->length - $this->scale); + // Apparently y-axis is inverted (y0 is at bottom and not top) in EPS, so we have to switch the y-axis here + $outputY = ($top - ($y * $this->scale)); + + return sprintf('%d %d %d %d F', $outputX, $outputY, $this->scale, $this->scale); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php new file mode 100644 index 000000000..a80295411 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php @@ -0,0 +1,177 @@ + $val){ + + if($i > 2){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):array{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + if(count($values) !== 3){ + throw new QRCodeOutputException('invalid color value'); + } + + return $values; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):array{ + return ($isDark) ? [0, 0, 0] : [255, 255, 255]; + } + + /** + * Initializes an FPDF instance + */ + protected function initFPDF():FPDF{ + return new FPDF('P', $this->options->fpdfMeasureUnit, $this->getOutputDimensions()); + } + + /** + * @inheritDoc + * + * @return string|\FPDF + */ + public function dump(string $file = null){ + $this->fpdf = $this->initFPDF(); + $this->fpdf->AddPage(); + + if($this::moduleValueIsValid($this->options->bgColor)){ + $bgColor = $this->prepareModuleValue($this->options->bgColor); + [$width, $height] = $this->getOutputDimensions(); + + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$bgColor); + $this->fpdf->Rect(0, 0, $width, $height, 'F'); + } + + $this->prevColor = null; + + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + + if($this->options->returnResource){ + return $this->fpdf; + } + + $pdfData = $this->fpdf->Output('S'); + + $this->saveToFile($pdfData, $file); + + if($this->options->outputBase64){ + $pdfData = $this->toBase64DataURI($pdfData); + } + + return $pdfData; + } + + /** + * Renders a single module + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($color !== null && $color !== $this->prevColor){ + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$color); + $this->prevColor = $color; + } + + $this->fpdf->Rect(($x * $this->scale), ($y * $this->scale), $this->scale, $this->scale, 'F'); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php new file mode 100644 index 000000000..f3ed6ea9d --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php @@ -0,0 +1,400 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use ErrorException; +use Throwable; +use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent, + imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, + imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, + restore_error_handler, set_error_handler, sprintf; +use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP; + +/** + * Converts the matrix into GD images, raw or base64 output (requires ext-gd) + * + * @see https://php.net/manual/book.image.php + * + * @deprecated 5.0.0 this class will be made abstract in future versions, + * calling it directly is deprecated - use one of the child classes instead + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ +class QRGdImage extends QROutputAbstract{ + + /** + * The GD image resource + * + * @see imagecreatetruecolor() + * @var resource|\GdImage + * + * @todo: add \GdImage type in v6 + */ + protected $image; + + /** + * The allocated background color + * + * @see \imagecolorallocate() + */ + protected int $background; + + /** + * Whether we're running in upscale mode (scale < 20) + * + * @see \chillerlan\QRCode\QROptions::$drawCircularModules + */ + protected bool $upscaled = false; + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + $this->options = $options; + $this->matrix = $matrix; + + $this->checkGD(); + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + } + + /** + * Checks whether GD is installed and if the given mode is supported + * + * @return void + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @codeCoverageIgnore + */ + protected function checkGD():void{ + + if(!extension_loaded('gd')){ + throw new QRCodeOutputException('ext-gd not loaded'); + } + + $modes = [ + self::GDIMAGE_BMP => IMG_BMP, + self::GDIMAGE_GIF => IMG_GIF, + self::GDIMAGE_JPG => IMG_JPG, + self::GDIMAGE_PNG => IMG_PNG, + self::GDIMAGE_WEBP => IMG_WEBP, + ]; + + // likely using default or custom output + if(!isset($modes[$this->options->outputType])){ + return; + } + + $mode = $modes[$this->options->outputType]; + + if((imagetypes() & $mode) !== $mode){ + throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType)); + } + + } + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first 3 values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):int{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $color = imagecolorallocate($this->image, ...$values); + + if($color === false){ + throw new QRCodeOutputException('could not set color: imagecolorallocate() error'); + } + + return $color; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):int{ + return $this->prepareModuleValue(($isDark) ? [0, 0, 0] : [255, 255, 255]); + } + + /** + * @inheritDoc + * + * @return string|resource|\GdImage + * + * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn + * @throws \ErrorException + */ + public function dump(string $file = null){ + + set_error_handler(function(int $errno, string $errstr):bool{ + throw new ErrorException($errstr, $errno); + }); + + $this->image = $this->createImage(); + // set module values after image creation because we need the GdImage instance + $this->setModuleValues(); + $this->setBgColor(); + + imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background); + + $this->drawImage(); + + if($this->upscaled){ + // scale down to the expected size + $this->image = imagescale($this->image, ($this->length / 10), ($this->length / 10)); + $this->upscaled = false; + } + + // set transparency after scaling, otherwise it would be undone + // @see https://www.php.net/manual/en/function.imagecolortransparent.php#77099 + $this->setTransparencyColor(); + + if($this->options->returnResource){ + restore_error_handler(); + + return $this->image; + } + + $imageData = $this->dumpImage(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + // @todo: remove mime parameter in v6 + $imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType); + } + + restore_error_handler(); + + return $imageData; + } + + /** + * Creates a new GdImage resource and scales it if necessary + * + * we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales + * + * @see https://github.com/chillerlan/php-qrcode/issues/23 + * + * @return \GdImage|resource + */ + protected function createImage(){ + + if($this->drawCircularModules && $this->options->gdImageUseUpscale && $this->options->scale < 20){ + // increase the initial image size by 10 + $this->length *= 10; + $this->scale *= 10; + $this->upscaled = true; + } + + return imagecreatetruecolor($this->length, $this->length); + } + + /** + * Sets the background color + */ + protected function setBgColor():void{ + + if(isset($this->background)){ + return; + } + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->background = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->background = $this->prepareModuleValue([255, 255, 255]); + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + // @todo: the jpg skip can be removed in v6 + if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->background; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + imagecolortransparent($this->image, $transparencyColor); + } + + /** + * Draws the QR image + */ + protected function drawImage():void{ + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + } + + /** + * Creates a single QR pixel with the given settings + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + imagefilledellipse( + $this->image, + (($x * $this->scale) + intdiv($this->scale, 2)), + (($y * $this->scale) + intdiv($this->scale, 2)), + (int)($this->circleDiameter * $this->scale), + (int)($this->circleDiameter * $this->scale), + $color + ); + + return; + } + + imagefilledrectangle( + $this->image, + ($x * $this->scale), + ($y * $this->scale), + (($x + 1) * $this->scale), + (($y + 1) * $this->scale), + $color + ); + } + + /** + * Renders the image with the gdimage function for the desired output + * + * @see \imagebmp() + * @see \imagegif() + * @see \imagejpeg() + * @see \imagepng() + * @see \imagewebp() + * + * @todo: v6.0: make abstract and call from child classes + * @see https://github.com/chillerlan/php-qrcode/issues/223 + * @codeCoverageIgnore + */ + protected function renderImage():void{ + + switch($this->options->outputType){ + case QROutputInterface::GDIMAGE_BMP: + imagebmp($this->image, null, ($this->options->quality > 0)); + break; + case QROutputInterface::GDIMAGE_GIF: + imagegif($this->image); + break; + case QROutputInterface::GDIMAGE_JPG: + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + break; + case QROutputInterface::GDIMAGE_WEBP: + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + break; + // silently default to png output + case QROutputInterface::GDIMAGE_PNG: + default: + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + + } + + /** + * Creates the final image by calling the desired GD output function + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function dumpImage():string{ + $exception = null; + $imageData = null; + + ob_start(); + + try{ + $this->renderImage(); + + $imageData = ob_get_contents(); + imagedestroy($this->image); + } + // not going to cover edge cases + // @codeCoverageIgnoreStart + catch(Throwable $e){ + $exception = $e; + } + // @codeCoverageIgnoreEnd + + ob_end_clean(); + + // throw here in case an exception happened within the output buffer + if($exception instanceof Throwable){ + throw new QRCodeOutputException($exception->getMessage()); + } + + return $imageData; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php new file mode 100644 index 000000000..268ebe7c2 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagebmp; + +/** + * GdImage bmp output + * + * @see \imagebmp() + */ +class QRGdImageBMP extends QRGdImage{ + + public const MIME_TYPE = 'image/bmp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagebmp($this->image, null, ($this->options->quality > 0)); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php new file mode 100644 index 000000000..a02130907 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagegif; + +/** + * GdImage gif output + * + * @see \imagegif() + */ +class QRGdImageGIF extends QRGdImage{ + + public const MIME_TYPE = 'image/gif'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagegif($this->image); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php new file mode 100644 index 000000000..6be36e2fe --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php @@ -0,0 +1,40 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagejpeg, max, min; + +/** + * GdImage jpeg output + * + * @see \imagejpeg() + */ +class QRGdImageJPEG extends QRGdImage{ + + public const MIME_TYPE = 'image/jpg'; + + /** + * @inheritDoc + */ + protected function setTransparencyColor():void{ + // noop - transparency is not supported + } + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php new file mode 100644 index 000000000..2db3fd5b4 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagepng, max, min; + +/** + * GdImage png output + * + * @see \imagepng() + */ +class QRGdImagePNG extends QRGdImage{ + + public const MIME_TYPE = 'image/png'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php new file mode 100644 index 000000000..cf8dfa9a5 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php @@ -0,0 +1,33 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagewebp, max, min; + +/** + * GdImage webp output + * + * @see \imagewebp() + */ +class QRGdImageWEBP extends QRGdImage{ + + public const MIME_TYPE = 'image/webp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php new file mode 100644 index 000000000..cda496d36 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRImage.php @@ -0,0 +1,19 @@ + + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +/** + * @deprecated 5.0.0 backward compatibility, use QRGdImage instead + * @see \chillerlan\QRCode\Output\QRGdImage + */ +class QRImage extends QRGdImage{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php new file mode 100644 index 000000000..cfd3a581f --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php @@ -0,0 +1,235 @@ + + * @copyright 2018 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use finfo, Imagick, ImagickDraw, ImagickPixel; +use function extension_loaded, in_array, is_string, max, min, preg_match, strlen; +use const FILEINFO_MIME_TYPE; + +/** + * ImageMagick output module (requires ext-imagick) + * + * @see https://php.net/manual/book.imagick.php + * @see https://phpimagick.com + */ +class QRImagick extends QROutputAbstract{ + + /** + * The main image instance + */ + protected Imagick $imagick; + + /** + * The main draw instance + */ + protected ImagickDraw $imagickDraw; + + /** + * The allocated background color + */ + protected ImagickPixel $backgroundColor; + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + + foreach(['fileinfo', 'imagick'] as $ext){ + if(!extension_loaded($ext)){ + throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore + } + } + + parent::__construct($options, $matrix); + } + + /** + * note: we're not necessarily validating the several values, just checking the general syntax + * + * @see https://www.php.net/manual/imagickpixel.construct.php + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim($value); + + // hex notation + // #rgb(a) + // #rrggbb(aa) + // #rrrrggggbbbb(aaaa) + // ... + if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){ + return true; + } + + // css (-like) func(...values) + if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){ + return true; + } + + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):ImagickPixel{ + return new ImagickPixel($value); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):ImagickPixel{ + return $this->prepareModuleValue(($isDark) ? '#000' : '#fff'); + } + + /** + * @inheritDoc + * + * @return string|\Imagick + */ + public function dump(string $file = null){ + $this->setBgColor(); + + $this->imagick = $this->createImage(); + + $this->drawImage(); + // set transparency color after all operations + $this->setTransparencyColor(); + + if($this->options->returnResource){ + return $this->imagick; + } + + $imageData = $this->imagick->getImageBlob(); + + $this->imagick->destroy(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData)); + } + + return $imageData; + } + + /** + * Sets the background color + */ + protected function setBgColor():void{ + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->backgroundColor = $this->prepareModuleValue('white'); + } + + /** + * Creates a new Imagick instance + */ + protected function createImage():Imagick{ + $imagick = new Imagick; + [$width, $height] = $this->getOutputDimensions(); + + $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat); + + if($this->options->quality > -1){ + $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality))); + } + + return $imagick; + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + if(!$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->backgroundColor; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false); + } + + /** + * Creates the QR image via ImagickDraw + */ + protected function drawImage():void{ + $this->imagickDraw = new ImagickDraw; + $this->imagickDraw->setStrokeWidth(0); + + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + + $this->imagick->drawImage($this->imagickDraw); + } + + /** + * draws a single pixel at the given position + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE)); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + $this->imagickDraw->circle( + (($x + 0.5) * $this->scale), + (($y + 0.5) * $this->scale), + (($x + 0.5 + $this->circleRadius) * $this->scale), + (($y + 0.5) * $this->scale) + ); + + return; + } + + $this->imagickDraw->rectangle( + ($x * $this->scale), + ($y * $this->scale), + ((($x + 1) * $this->scale) - 1), + ((($y + 1) * $this->scale) - 1) + ); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php new file mode 100644 index 000000000..22551e3e6 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php @@ -0,0 +1,94 @@ + + * @copyright 2016 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function is_string, preg_match, strip_tags, trim; + +/** + * Abstract for markup types: HTML, SVG, ... XML anyone? + */ +abstract class QRMarkup extends QROutputAbstract{ + + /** + * note: we're not necessarily validating the several values, just checking the general syntax + * note: css4 colors are not included + * + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim(strip_tags($value), " '\"\r\n\t"); + + // hex notation + // #rgb(a) + // #rrggbb(aa) + if(preg_match('/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i', $value)){ + return true; + } + + // css: hsla/rgba(...values) + if(preg_match('#^(hsla?|rgba?)\([\d .,%/]+\)$#i', $value)){ + return true; + } + + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return trim(strip_tags($value), " '\"\r\n\t"); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '#000' : '#fff'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + $data = $this->createMarkup($file !== null); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * returns a string with all css classes for the current element + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return $this->options->cssClass; + } + + /** + * returns the fully parsed and rendered markup string for the given input + */ + abstract protected function createMarkup(bool $saveToFile):string; + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php new file mode 100644 index 000000000..65dc49a8a --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php @@ -0,0 +1,51 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function implode, sprintf; + +/** + * HTML output (a cheap markup substitute when SVG is not available or not an option) + */ +class QRMarkupHTML extends QRMarkup{ + + public const MIME_TYPE = 'text/html'; + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $rows = []; + $cssClass = $this->getCssClass(); + + foreach($this->matrix->getMatrix() as $row){ + $element = ''; + $modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row); + + $rows[] = sprintf('
%s
%s', implode('', $modules), $this->eol); + } + + $html = sprintf('
%3$s%2$s
%3$s', $cssClass, implode('', $rows), $this->eol); + + // wrap the snippet into a body when saving to file + if($saveToFile){ + $html = sprintf( + '%2$s%2$s%2$s'. + 'QR Code%2$s%1$s%2$s', + $html, + $this->eol + ); + } + + return $html; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php new file mode 100644 index 000000000..735c4180b --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php @@ -0,0 +1,200 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_chunk, implode, is_string, preg_match, sprintf, trim; + +/** + * SVG output + * + * @see https://github.com/codemasher/php-qrcode/pull/5 + * @see https://developer.mozilla.org/en-US/docs/Web/SVG + * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/ + * @see https://lea.verou.me/blog/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/ + * @see https://codepen.io/leaverou/full/RmwzKv + * @see https://jakearchibald.github.io/svgomg/ + * @see https://web.archive.org/web/20200220211445/http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html + */ +class QRMarkupSVG extends QRMarkup{ + + public const MIME_TYPE = 'image/svg+xml'; + + /** + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim($value); + + // url(...) + if(preg_match('~^url\([-/#a-z\d]+\)$~i', $value)){ + return true; + } + + // otherwise check for standard css notation + return parent::moduleValueIsValid($value); + } + + /** + * @inheritDoc + */ + protected function getOutputDimensions():array{ + return [$this->moduleCount, $this->moduleCount]; + } + + /** + * @inheritDoc + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return implode(' ', [ + 'qr-'.($this::LAYERNAMES[$M_TYPE] ?? $M_TYPE), + $this->matrix->isDark($M_TYPE) ? 'dark' : 'light', + $this->options->cssClass, + ]); + } + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $svg = $this->header(); + + if(!empty($this->options->svgDefs)){ + $svg .= sprintf('%1$s%2$s%2$s', $this->options->svgDefs, $this->eol); + } + + $svg .= $this->paths(); + + // close svg + $svg .= sprintf('%1$s%1$s', $this->eol); + + // transform to data URI only when not saving to file + if(!$saveToFile && $this->options->outputBase64){ + $svg = $this->toBase64DataURI($svg); + } + + return $svg; + } + + /** + * returns the value for the SVG viewBox attribute + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox + * @see https://css-tricks.com/scale-svg/#article-header-id-3 + */ + protected function getViewBox():string{ + [$width, $height] = $this->getOutputDimensions(); + + return sprintf('0 0 %s %s', $width, $height); + } + + /** + * returns the header with the given options parsed + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg + */ + protected function header():string{ + + $header = sprintf( + '%4$s', + $this->options->cssClass, + $this->getViewBox(), + $this->options->svgPreserveAspectRatio, + $this->eol + ); + + if($this->options->svgAddXmlHeader){ + $header = sprintf('%s%s', $this->eol, $header); + } + + return $header; + } + + /** + * returns one or more SVG elements + */ + protected function paths():string{ + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + $svg = []; + + // create the path elements + foreach($paths as $M_TYPE => $modules){ + // limit the total line length + $chunks = array_chunk($modules, 100); + $chonks = []; + + foreach($chunks as $chunk){ + $chonks[] = implode(' ', $chunk); + } + + $path = implode($this->eol, $chonks); + + if(empty($path)){ + continue; + } + + $svg[] = $this->path($path, $M_TYPE); + } + + return implode($this->eol, $svg); + } + + /** + * renders and returns a single element + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path + */ + protected function path(string $path, int $M_TYPE):string{ + + if($this->options->svgUseFillAttributes){ + return sprintf( + '', + $this->getCssClass($M_TYPE), + $this->getModuleValue($M_TYPE), + $path + ); + } + + return sprintf('', $this->getCssClass($M_TYPE), $path); + } + + /** + * returns a path segment for a single module + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + // string interpolation: ugly and fast + $ix = ($x + 0.5 - $this->circleRadius); + $iy = ($y + 0.5); + + // phpcs:ignore + return "M$ix $iy a$this->circleRadius $this->circleRadius 0 1 0 $this->circleDiameter 0 a$this->circleRadius $this->circleRadius 0 1 0 -$this->circleDiameter 0Z"; + } + + // phpcs:ignore + return "M$x $y h1 v1 h-1Z"; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php new file mode 100644 index 000000000..7ac64fffa --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php @@ -0,0 +1,261 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use Closure; +use function base64_encode, dirname, file_put_contents, is_writable, ksort, sprintf; + +/** + * common output abstract + */ +abstract class QROutputAbstract implements QROutputInterface{ + + /** + * the current size of the QR matrix + * + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() + */ + protected int $moduleCount; + + /** + * the side length of the QR image (modules * scale) + */ + protected int $length; + + /** + * an (optional) array of color values for the several QR matrix parts + */ + protected array $moduleValues; + + /** + * the (filled) data matrix object + */ + protected QRMatrix $matrix; + + /** + * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions + */ + protected SettingsContainerInterface $options; + + /** @see \chillerlan\QRCode\QROptions::$scale */ + protected int $scale; + /** @see \chillerlan\QRCode\QROptions::$connectPaths */ + protected bool $connectPaths; + /** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */ + protected array $excludeFromConnect; + /** @see \chillerlan\QRCode\QROptions::$eol */ + protected string $eol; + /** @see \chillerlan\QRCode\QROptions::$drawLightModules */ + protected bool $drawLightModules; + /** @see \chillerlan\QRCode\QROptions::$drawCircularModules */ + protected bool $drawCircularModules; + /** @see \chillerlan\QRCode\QROptions::$keepAsSquare */ + protected array $keepAsSquare; + /** @see \chillerlan\QRCode\QROptions::$circleRadius */ + protected float $circleRadius; + protected float $circleDiameter; + + /** + * QROutputAbstract constructor. + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + $this->options = $options; + $this->matrix = $matrix; + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + $this->setModuleValues(); + } + + /** + * Creates copies of several QROptions values to avoid calling the magic getters + * in long loops for a significant performance increase. + * + * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40). + */ + protected function copyVars():void{ + + $vars = [ + 'connectPaths', + 'excludeFromConnect', + 'eol', + 'drawLightModules', + 'drawCircularModules', + 'keepAsSquare', + 'circleRadius', + ]; + + foreach($vars as $property){ + $this->{$property} = $this->options->{$property}; + } + + $this->circleDiameter = ($this->circleRadius * 2); + } + + /** + * Sets/updates the matrix dimensions + * + * Call this method if you modify the matrix from within your custom module in case the dimensions have been changed + */ + protected function setMatrixDimensions():void{ + $this->moduleCount = $this->matrix->getSize(); + $this->scale = $this->options->scale; + $this->length = ($this->moduleCount * $this->scale); + } + + /** + * Returns a 2 element array with the current output width and height + * + * The type and units of the values depend on the output class. The default value is the current module count * scale. + */ + protected function getOutputDimensions():array{ + return [$this->length, $this->length]; + } + + /** + * Sets the initial module values + */ + protected function setModuleValues():void{ + + // first fill the map with the default values + foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ + $this->moduleValues[$M_TYPE] = $this->getDefaultModuleValue($defaultValue); + } + + // now loop over the options values to replace defaults and add extra values + foreach($this->options->moduleValues as $M_TYPE => $value){ + if($this::moduleValueIsValid($value)){ + $this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value); + } + } + + } + + /** + * Prepares the value for the given input (return value depends on the output class) + * + * @param mixed $value + * + * @return mixed|null + */ + abstract protected function prepareModuleValue($value); + + /** + * Returns a default value for either dark or light modules (return value depends on the output class) + * + * @return mixed|null + */ + abstract protected function getDefaultModuleValue(bool $isDark); + + /** + * Returns the prepared value for the given $M_TYPE + * + * @return mixed return value depends on the output class + * @throws \chillerlan\QRCode\Output\QRCodeOutputException if $moduleValues[$M_TYPE] doesn't exist + */ + protected function getModuleValue(int $M_TYPE){ + + if(!isset($this->moduleValues[$M_TYPE])){ + throw new QRCodeOutputException(sprintf('$M_TYPE %012b not found in module values map', $M_TYPE)); + } + + return $this->moduleValues[$M_TYPE]; + } + + /** + * Returns the prepared module value at the given coordinate [$x, $y] (convenience) + * + * @return mixed|null + */ + protected function getModuleValueAt(int $x, int $y){ + return $this->getModuleValue($this->matrix->get($x, $y)); + } + + /** + * Returns a base64 data URI for the given string and mime type + */ + protected function toBase64DataURI(string $data, string $mime = null):string{ + return sprintf('data:%s;base64,%s', ($mime ?? $this::MIME_TYPE), base64_encode($data)); + } + + /** + * Saves the qr $data to a $file. If $file is null, nothing happens. + * + * @see file_put_contents() + * @see \chillerlan\QRCode\QROptions::$cachefile + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function saveToFile(string $data, string $file = null):void{ + + if($file === null){ + return; + } + + if(!is_writable(dirname($file))){ + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s', $file)); + } + + if(file_put_contents($file, $data) === false){ + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s (file_put_contents error)', $file)); + } + } + + /** + * collects the modules per QRMatrix::M_* type and runs a $transform function on each module and + * returns an array with the transformed modules + * + * The transform callback is called with the following parameters: + * + * $x - current column + * $y - current row + * $M_TYPE - field value + * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id + */ + protected function collectModules(Closure $transform):array{ + $paths = []; + + // collect the modules for each type + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $M_TYPE_LAYER = $M_TYPE; + + if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){ + // to connect paths we'll redeclare the $M_TYPE_LAYER to data only + $M_TYPE_LAYER = QRMatrix::M_DATA; + + if($this->matrix->isDark($M_TYPE)){ + $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; + } + } + + // collect the modules per $M_TYPE + $module = $transform($x, $y, $M_TYPE, $M_TYPE_LAYER); + + if(!empty($module)){ + $paths[$M_TYPE_LAYER][] = $module; + } + } + } + + // beautify output + ksort($paths); + + return $paths; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php new file mode 100644 index 000000000..919f1ba75 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php @@ -0,0 +1,226 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; + +/** + * Converts the data matrix into readable output + */ +interface QROutputInterface{ + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_HTML = 'html'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_SVG = 'svg'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_BMP = 'bmp'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_GIF = 'gif'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_JPG = 'jpg'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_PNG = 'png'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_WEBP = 'webp'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_JSON = 'json'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_TEXT = 'text'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const IMAGICK = 'imagick'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const FPDF = 'fpdf'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const EPS = 'eps'; + + /** + * @var string + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const CUSTOM = 'custom'; + + /** + * Map of built-in output modes => class FQN + * + * @var string[] + * @deprecated 5.0.0 + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MODES = [ + self::MARKUP_SVG => QRMarkupSVG::class, + self::MARKUP_HTML => QRMarkupHTML::class, + self::GDIMAGE_BMP => QRGdImageBMP::class, + self::GDIMAGE_GIF => QRGdImageGIF::class, + self::GDIMAGE_JPG => QRGdImageJPEG::class, + self::GDIMAGE_PNG => QRGdImagePNG::class, + self::GDIMAGE_WEBP => QRGdImageWEBP::class, + self::STRING_JSON => QRStringJSON::class, + self::STRING_TEXT => QRStringText::class, + self::IMAGICK => QRImagick::class, + self::FPDF => QRFpdf::class, + self::EPS => QREps::class, + ]; + + /** + * Map of module type => default value + * + * @var bool[] + */ + public const DEFAULT_MODULE_VALUES = [ + // light + QRMatrix::M_NULL => false, + QRMatrix::M_DARKMODULE_LIGHT => false, + QRMatrix::M_DATA => false, + QRMatrix::M_FINDER => false, + QRMatrix::M_SEPARATOR => false, + QRMatrix::M_ALIGNMENT => false, + QRMatrix::M_TIMING => false, + QRMatrix::M_FORMAT => false, + QRMatrix::M_VERSION => false, + QRMatrix::M_QUIETZONE => false, + QRMatrix::M_LOGO => false, + QRMatrix::M_FINDER_DOT_LIGHT => false, + // dark + QRMatrix::M_DARKMODULE => true, + QRMatrix::M_DATA_DARK => true, + QRMatrix::M_FINDER_DARK => true, + QRMatrix::M_SEPARATOR_DARK => true, + QRMatrix::M_ALIGNMENT_DARK => true, + QRMatrix::M_TIMING_DARK => true, + QRMatrix::M_FORMAT_DARK => true, + QRMatrix::M_VERSION_DARK => true, + QRMatrix::M_QUIETZONE_DARK => true, + QRMatrix::M_LOGO_DARK => true, + QRMatrix::M_FINDER_DOT => true, + ]; + + /** + * Map of module type => readable name (for CSS etc.) + * + * @var string[] + */ + public const LAYERNAMES = [ + // light + QRMatrix::M_NULL => 'null', + QRMatrix::M_DARKMODULE_LIGHT => 'darkmodule-light', + QRMatrix::M_DATA => 'data', + QRMatrix::M_FINDER => 'finder', + QRMatrix::M_SEPARATOR => 'separator', + QRMatrix::M_ALIGNMENT => 'alignment', + QRMatrix::M_TIMING => 'timing', + QRMatrix::M_FORMAT => 'format', + QRMatrix::M_VERSION => 'version', + QRMatrix::M_QUIETZONE => 'quietzone', + QRMatrix::M_LOGO => 'logo', + QRMatrix::M_FINDER_DOT_LIGHT => 'finder-dot-light', + // dark + QRMatrix::M_DARKMODULE => 'darkmodule', + QRMatrix::M_DATA_DARK => 'data-dark', + QRMatrix::M_FINDER_DARK => 'finder-dark', + QRMatrix::M_SEPARATOR_DARK => 'separator-dark', + QRMatrix::M_ALIGNMENT_DARK => 'alignment-dark', + QRMatrix::M_TIMING_DARK => 'timing-dark', + QRMatrix::M_FORMAT_DARK => 'format-dark', + QRMatrix::M_VERSION_DARK => 'version-dark', + QRMatrix::M_QUIETZONE_DARK => 'quietzone-dark', + QRMatrix::M_LOGO_DARK => 'logo-dark', + QRMatrix::M_FINDER_DOT => 'finder-dot', + ]; + + /** + * @var string + * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI() + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const MIME_TYPE = ''; + + /** + * Determines whether the given value is valid + * + * @param mixed $value + */ + public static function moduleValueIsValid($value):bool; + + /** + * Generates the output, optionally dumps it to a file, and returns it + * + * please note that the value of QROptions::$cachefile is already evaluated at this point. + * if the output module is invoked manually, it has no effect at all. + * you need to supply the $file parameter here in that case (or handle the option value in your custom output module). + * + * @see \chillerlan\QRCode\QRCode::renderMatrix() + * + * @return mixed + */ + public function dump(string $file = null); + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php new file mode 100644 index 000000000..cb70a6cce --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRString.php @@ -0,0 +1,111 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function implode, is_string, json_encode, max, min, sprintf; +use const JSON_THROW_ON_ERROR; + +/** + * Converts the matrix data into string types + * + * @deprecated 5.0.0 this class will be removed in future versions, use one of QRStringText or QRStringJSON instead + */ +class QRString extends QROutputAbstract{ + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + + switch($this->options->outputType){ + case QROutputInterface::STRING_TEXT: + $data = $this->text(); + break; + case QROutputInterface::STRING_JSON: + default: + $data = $this->json(); + } + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * string output + */ + protected function text():string{ + $lines = []; + $linestart = $this->options->textLineStart; + + for($y = 0; $y < $this->moduleCount; $y++){ + $r = []; + + for($x = 0; $x < $this->moduleCount; $x++){ + $r[] = $this->getModuleValueAt($x, $y); + } + + $lines[] = $linestart.implode('', $r); + } + + return implode($this->eol, $lines); + } + + /** + * JSON output + * + * @throws \JsonException + */ + protected function json():string{ + return json_encode($this->matrix->getMatrix($this->options->jsonAsBooleans), JSON_THROW_ON_ERROR); + } + + // + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php new file mode 100644 index 000000000..6f2e7d5e0 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php @@ -0,0 +1,67 @@ + + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function json_encode; + +/** + * + */ +class QRStringJSON extends QROutputAbstract{ + + public const MIME_TYPE = 'application/json'; + + /** + * @inheritDoc + * @throws \JsonException + */ + public function dump(string $file = null):string{ + $matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans); + $data = json_encode($matrix, $this->options->jsonFlags);; + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function prepareModuleValue($value):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + public static function moduleValueIsValid($value):bool{ + return true; + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php new file mode 100644 index 000000000..f475a2a68 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php @@ -0,0 +1,76 @@ + + * @copyright 2023 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_map, implode, is_string, max, min, sprintf; + +/** + * + */ +class QRStringText extends QROutputAbstract{ + + public const MIME_TYPE = 'text/plain'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + + /** + * @inheritDoc + */ + public function dump(string $file = null):string{ + $lines = []; + $linestart = $this->options->textLineStart; + + foreach($this->matrix->getMatrix() as $row){ + $lines[] = $linestart.implode('', array_map([$this, 'getModuleValue'], $row)); + } + + $data = implode($this->eol, $lines); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QRCode.php b/src/vendor/chillerlan/php-qrcode/src/QRCode.php new file mode 100644 index 000000000..bca6ac584 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QRCode.php @@ -0,0 +1,488 @@ + + * @copyright 2015 Smiley + * @license MIT + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + +namespace chillerlan\QRCode; + +use chillerlan\QRCode\Common\{ + EccLevel, ECICharset, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface, MaskPattern, Mode, Version +}; +use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix}; +use chillerlan\QRCode\Decoder\{Decoder, DecoderResult}; +use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface}; +use chillerlan\Settings\SettingsContainerInterface; +use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding; + +/** + * Turns a text string into a Model 2 QR Code + * + * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php + * @see https://www.qrcode.com/en/codes/model12.html + * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf + * @see https://en.wikipedia.org/wiki/QR_code + * @see https://www.thonky.com/qr-code-tutorial/ + */ +class QRCode{ + + /** + * @deprecated 5.0.0 use Version::AUTO instead + * @see \chillerlan\QRCode\Common\Version::AUTO + * @var int + */ + public const VERSION_AUTO = Version::AUTO; + + /** + * @deprecated 5.0.0 use MaskPattern::AUTO instead + * @see \chillerlan\QRCode\Common\MaskPattern::AUTO + * @var int + */ + public const MASK_PATTERN_AUTO = MaskPattern::AUTO; + + /** + * @deprecated 5.0.0 use EccLevel::L instead + * @see \chillerlan\QRCode\Common\EccLevel::L + * @var int + */ + public const ECC_L = EccLevel::L; + + /** + * @deprecated 5.0.0 use EccLevel::M instead + * @see \chillerlan\QRCode\Common\EccLevel::M + * @var int + */ + public const ECC_M = EccLevel::M; + + /** + * @deprecated 5.0.0 use EccLevel::Q instead + * @see \chillerlan\QRCode\Common\EccLevel::Q + * @var int + */ + public const ECC_Q = EccLevel::Q; + + /** + * @deprecated 5.0.0 use EccLevel::H instead + * @see \chillerlan\QRCode\Common\EccLevel::H + * @var int + */ + public const ECC_H = EccLevel::H; + + /** + * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML + * @var string + */ + public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML; + + /** + * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG + * @var string + */ + public const OUTPUT_MARKUP_SVG = QROutputInterface::MARKUP_SVG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG + * @var string + */ + public const OUTPUT_IMAGE_PNG = QROutputInterface::GDIMAGE_PNG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG + * @var string + */ + public const OUTPUT_IMAGE_JPG = QROutputInterface::GDIMAGE_JPG; + + /** + * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead + * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF + * @var string + */ + public const OUTPUT_IMAGE_GIF = QROutputInterface::GDIMAGE_GIF; + + /** + * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead + * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON + * @var string + */ + public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON; + + /** + * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead + * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT + * @var string + */ + public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT; + + /** + * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead + * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK + * @var string + */ + public const OUTPUT_IMAGICK = QROutputInterface::IMAGICK; + + /** + * @deprecated 5.0.0 use QROutputInterface::FPDF instead + * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF + * @var string + */ + public const OUTPUT_FPDF = QROutputInterface::FPDF; + + /** + * @deprecated 5.0.0 use QROutputInterface::EPS instead + * @see \chillerlan\QRCode\Output\QROutputInterface::EPS + * @var string + */ + public const OUTPUT_EPS = QROutputInterface::EPS; + + /** + * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead + * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM + * @var string + */ + public const OUTPUT_CUSTOM = QROutputInterface::CUSTOM; + + /** + * @deprecated 5.0.0 use QROutputInterface::MODES instead + * @see \chillerlan\QRCode\Output\QROutputInterface::MODES + * @var string[] + */ + public const OUTPUT_MODES = QROutputInterface::MODES; + + /** + * The settings container + * + * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface + */ + protected SettingsContainerInterface $options; + + /** + * A collection of one or more data segments of QRDataModeInterface instances to write + * + * @var \chillerlan\QRCode\Data\QRDataModeInterface[] + */ + protected array $dataSegments = []; + + /** + * The luminance source for the reader + */ + protected string $luminanceSourceFQN = GDLuminanceSource::class; + + /** + * QRCode constructor. + * + * PHP8: accept iterable + */ + public function __construct(SettingsContainerInterface $options = null){ + $this->setOptions(($options ?? new QROptions)); + } + + /** + * Sets an options instance + */ + public function setOptions(SettingsContainerInterface $options):self{ + $this->options = $options; + + if($this->options->readerUseImagickIfAvailable){ + $this->luminanceSourceFQN = IMagickLuminanceSource::class; + } + + return $this; + } + + /** + * Renders a QR Code for the given $data and QROptions, saves $file optionally + * + * Note: it is possible to add several data segments before calling this method with a valid $data string + * which will result in a mixed-mode QR Code with the given parameter as last element. + * + * @see https://github.com/chillerlan/php-qrcode/issues/246 + * + * @return mixed + */ + public function render(string $data = null, string $file = null){ + + if($data !== null){ + /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */ + foreach(Mode::INTERFACES as $dataInterface){ + + if($dataInterface::validateString($data)){ + $this->addSegment(new $dataInterface($data)); + + break; + } + } + } + + return $this->renderMatrix($this->getQRMatrix(), $file); + } + + /** + * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally + * + * @return mixed + */ + public function renderMatrix(QRMatrix $matrix, string $file = null){ + return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile); + } + + /** + * Returns a QRMatrix object for the given $data and current QROptions + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function getQRMatrix():QRMatrix{ + $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix(); + + $maskPattern = $this->options->maskPattern === MaskPattern::AUTO + ? MaskPattern::getBestPattern($matrix) + : new MaskPattern($this->options->maskPattern); + + $matrix->setFormatInfo($maskPattern)->mask($maskPattern); + + return $this->addMatrixModifications($matrix); + } + + /** + * add matrix modifications after mask pattern evaluation and before handing over to output + */ + protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{ + + if($this->options->addLogoSpace){ + // check whether one of the dimensions was omitted + $logoSpaceWidth = ($this->options->logoSpaceWidth ?? $this->options->logoSpaceHeight ?? 0); + $logoSpaceHeight = ($this->options->logoSpaceHeight ?? $logoSpaceWidth); + + $matrix->setLogoSpace( + $logoSpaceWidth, + $logoSpaceHeight, + $this->options->logoSpaceStartX, + $this->options->logoSpaceStartY + ); + } + + if($this->options->addQuietzone){ + $matrix->setQuietZone($this->options->quietzoneSize); + } + + return $matrix; + } + + /** + * @deprecated 5.0.0 use QRCode::getQRMatrix() instead + * @see \chillerlan\QRCode\QRCode::getQRMatrix() + * @codeCoverageIgnore + */ + public function getMatrix():QRMatrix{ + return $this->getQRMatrix(); + } + + /** + * initializes a fresh built-in or custom QROutputInterface + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{ + // @todo: remove custom invocation in v6 + $outputInterface = (QROutputInterface::MODES[$this->options->outputType] ?? null); + + if($this->options->outputType === QROutputInterface::CUSTOM){ + $outputInterface = $this->options->outputInterface; + } + + if(!$outputInterface || !class_exists($outputInterface)){ + throw new QRCodeOutputException('invalid output module'); + } + + if(!in_array(QROutputInterface::class, class_implements($outputInterface))){ + throw new QRCodeOutputException('output module does not implement QROutputInterface'); + } + + return new $outputInterface($this->options, $matrix); + } + + /** + * checks if a string qualifies as numeric (convenience method) + * + * @deprecated 5.0.0 use Number::validateString() instead + * @see \chillerlan\QRCode\Data\Number::validateString() + * @codeCoverageIgnore + */ + public function isNumber(string $string):bool{ + return Number::validateString($string); + } + + /** + * checks if a string qualifies as alphanumeric (convenience method) + * + * @deprecated 5.0.0 use AlphaNum::validateString() instead + * @see \chillerlan\QRCode\Data\AlphaNum::validateString() + * @codeCoverageIgnore + */ + public function isAlphaNum(string $string):bool{ + return AlphaNum::validateString($string); + } + + /** + * checks if a string qualifies as Kanji (convenience method) + * + * @deprecated 5.0.0 use Kanji::validateString() instead + * @see \chillerlan\QRCode\Data\Kanji::validateString() + * @codeCoverageIgnore + */ + public function isKanji(string $string):bool{ + return Kanji::validateString($string); + } + + /** + * a dummy (convenience method) + * + * @deprecated 5.0.0 use Byte::validateString() instead + * @see \chillerlan\QRCode\Data\Byte::validateString() + * @codeCoverageIgnore + */ + public function isByte(string $string):bool{ + return Byte::validateString($string); + } + + /** + * Adds a data segment + * + * ISO/IEC 18004:2000 8.3.6 - Mixing modes + * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length + */ + public function addSegment(QRDataModeInterface $segment):self{ + $this->dataSegments[] = $segment; + + return $this; + } + + /** + * Clears the data segments array + * + * @codeCoverageIgnore + */ + public function clearSegments():self{ + $this->dataSegments = []; + + return $this; + } + + /** + * Adds a numeric data segment + * + * ISO/IEC 18004:2000 8.3.2 - Numeric Mode + */ + public function addNumericSegment(string $data):self{ + return $this->addSegment(new Number($data)); + } + + /** + * Adds an alphanumeric data segment + * + * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode + */ + public function addAlphaNumSegment(string $data):self{ + return $this->addSegment(new AlphaNum($data)); + } + + /** + * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS) + * + * ISO/IEC 18004:2000 8.3.5 - Kanji Mode + */ + public function addKanjiSegment(string $data):self{ + return $this->addSegment(new Kanji($data)); + } + + /** + * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030) + * + * GBT18284-2000 Hanzi Mode + */ + public function addHanziSegment(string $data):self{ + return $this->addSegment(new Hanzi($data)); + } + + /** + * Adds an 8-bit byte data segment + * + * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode + */ + public function addByteSegment(string $data):self{ + return $this->addSegment(new Byte($data)); + } + + /** + * Adds a standalone ECI designator + * + * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset + * + * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode + */ + public function addEciDesignator(int $encoding):self{ + return $this->addSegment(new ECI($encoding)); + } + + /** + * Adds an ECI data segment (including designator) + * + * The given string will be encoded from mb_internal_encoding() to the given ECI character set + * + * I hate this somehow, but I'll leave it for now + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function addEciSegment(int $encoding, string $data):self{ + // validate the encoding id + $eciCharset = new ECICharset($encoding); + // get charset name + $eciCharsetName = $eciCharset->getName(); + // convert the string to the given charset + if($eciCharsetName !== null){ + $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding()); + + return $this + ->addEciDesignator($eciCharset->getID()) + ->addByteSegment($data) + ; + } + + throw new QRCodeException('unable to add ECI segment'); + } + + /** + * Reads a QR Code from a given file + * + * @noinspection PhpUndefinedMethodInspection + */ + public function readFromFile(string $path):DecoderResult{ + return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options)); + } + + /** + * Reads a QR Code from the given data blob + * + * @noinspection PhpUndefinedMethodInspection + */ + public function readFromBlob(string $blob):DecoderResult{ + return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options)); + } + + /** + * Reads a QR Code from the given luminance source + */ + public function readFromSource(LuminanceSourceInterface $source):DecoderResult{ + return (new Decoder)->decode($source); + } + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php b/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php new file mode 100644 index 000000000..600ce50ea --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QRCodeException.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode; + +use Exception; + +/** + * An exception container + */ +class QRCodeException extends Exception{ + +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QROptions.php b/src/vendor/chillerlan/php-qrcode/src/QROptions.php new file mode 100644 index 000000000..91b5b4568 --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QROptions.php @@ -0,0 +1,20 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode; + +use chillerlan\Settings\SettingsContainerAbstract; + +/** + * The QRCode settings container + */ +class QROptions extends SettingsContainerAbstract{ + use QROptionsTrait; +} diff --git a/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php b/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php new file mode 100644 index 000000000..d2bc8c2ce --- /dev/null +++ b/src/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php @@ -0,0 +1,729 @@ + + * @copyright 2018 smiley + * @license MIT + * + * @noinspection PhpUnused, PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode; + +use chillerlan\QRCode\Output\QROutputInterface; +use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version}; +use function extension_loaded, in_array, max, min, strtolower; +use const JSON_THROW_ON_ERROR, PHP_EOL; + +/** + * The QRCode plug-in settings & setter functionality + */ +trait QROptionsTrait{ + + /* + * QR Code specific settings + */ + + /** + * QR Code version number + * + * `1 ... 40` or `Version::AUTO` (default) + * + * @see \chillerlan\QRCode\Common\Version + */ + protected int $version = Version::AUTO; + + /** + * Minimum QR version + * + * if `QROptions::$version` is set to `Version::AUTO` (default: 1) + */ + protected int $versionMin = 1; + + /** + * Maximum QR version + * + * if `QROptions::$version` is set to `Version::AUTO` (default: 40) + */ + protected int $versionMax = 40; + + /** + * Error correct level + * + * `EccLevel::X` where `X` is: + * + * - `L` => 7% (default) + * - `M` => 15% + * - `Q` => 25% + * - `H` => 30% + * + * @todo: accept string values (PHP8+) + * @see \chillerlan\QRCode\Common\EccLevel + * @see https://github.com/chillerlan/php-qrcode/discussions/160 + */ + protected int $eccLevel = EccLevel::L; + + /** + * Mask Pattern to use (no value in using, mostly for unit testing purposes) + * + * `0 ... 7` or `MaskPattern::PATTERN_AUTO` (default) + * + * @see \chillerlan\QRCode\Common\MaskPattern + */ + protected int $maskPattern = MaskPattern::AUTO; + + /** + * Add a "quiet zone" (margin) according to the QR code spec + * + * @see https://www.qrcode.com/en/howto/code.html + */ + protected bool $addQuietzone = true; + + /** + * Size of the quiet zone + * + * internally clamped to `0 ... $moduleCount / 2` (default: 4) + */ + protected int $quietzoneSize = 4; + + + /* + * General output settings + */ + + /** + * The built-in output type + * + * - `QROutputInterface::MARKUP_SVG` (default) + * - `QROutputInterface::MARKUP_HTML` + * - `QROutputInterface::GDIMAGE_BMP` + * - `QROutputInterface::GDIMAGE_GIF` + * - `QROutputInterface::GDIMAGE_JPG` + * - `QROutputInterface::GDIMAGE_PNG` + * - `QROutputInterface::GDIMAGE_WEBP` + * - `QROutputInterface::STRING_TEXT` + * - `QROutputInterface::STRING_JSON` + * - `QROutputInterface::IMAGICK` + * - `QROutputInterface::EPS` + * - `QROutputInterface::FPDF` + * - `QROutputInterface::CUSTOM` + * + * @see \chillerlan\QRCode\Output\QREps + * @see \chillerlan\QRCode\Output\QRFpdf + * @see \chillerlan\QRCode\Output\QRGdImage + * @see \chillerlan\QRCode\Output\QRImagick + * @see \chillerlan\QRCode\Output\QRMarkupHTML + * @see \chillerlan\QRCode\Output\QRMarkupSVG + * @see \chillerlan\QRCode\Output\QRString + * @see https://github.com/chillerlan/php-qrcode/issues/223 + * + * @deprecated 5.0.0 see issue #223 + */ + protected string $outputType = QROutputInterface::MARKUP_SVG; + + /** + * The FQCN of the custom `QROutputInterface` + * + * if `QROptions::$outputType` is set to `QROutputInterface::CUSTOM` (default: `null`) + * + * @deprecated 5.0.0 the nullable type will be removed in future versions + * and the default value will be set to `QRMarkupSVG::class` + */ + protected ?string $outputInterface = null; + + /** + * Return the image resource instead of a render if applicable. + * + * - `QRGdImage`: `resource` (PHP < 8), `GdImage` + * - `QRImagick`: `Imagick` + * - `QRFpdf`: `FPDF` + * + * This option overrides/ignores other output settings, such as `QROptions::$cachefile` + * and `QROptions::$outputBase64`. (default: `false`) + * + * @see \chillerlan\QRCode\Output\QROutputInterface::dump() + */ + protected bool $returnResource = false; + + /** + * Optional cache file path `/path/to/cache.file` + * + * Please note that the `$file` parameter in `QRCode::render()` and `QRCode::renderMatrix()` + * takes precedence over the `QROptions::$cachefile` value. (default: `null`) + * + * @see \chillerlan\QRCode\QRCode::render() + * @see \chillerlan\QRCode\QRCode::renderMatrix() + */ + protected ?string $cachefile = null; + + /** + * Toggle base64 data URI or raw data output (if applicable) + * + * (default: `true`) + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI() + */ + protected bool $outputBase64 = true; + + /** + * Newline string + * + * (default: `PHP_EOL`) + */ + protected string $eol = PHP_EOL; + + /* + * Common visual modifications + */ + + /** + * Sets the image background color (if applicable) + * + * - `QRImagick`: defaults to `"white"` + * - `QRGdImage`: defaults to `[255, 255, 255]` + * - `QRFpdf`: defaults to blank internally (white page) + * + * @var mixed|null + */ + protected $bgColor = null; + + /** + * Whether to invert the matrix (reflectance reversal) + * + * (default: `false`) + * + * @see \chillerlan\QRCode\Data\QRMatrix::invert() + */ + protected bool $invertMatrix = false; + + /** + * Whether to draw the light (false) modules + * + * (default: `true`) + */ + protected bool $drawLightModules = true; + + /** + * Specify whether to draw the modules as filled circles + * + * a note for `GdImage` output: + * + * if `QROptions::$scale` is less than 20, the image will be upscaled internally, then the modules will be drawn + * using `imagefilledellipse()` and then scaled back to the expected size + * + * No effect in: `QREps`, `QRFpdf`, `QRMarkupHTML` + * + * @see \imagefilledellipse() + * @see https://github.com/chillerlan/php-qrcode/issues/23 + * @see https://github.com/chillerlan/php-qrcode/discussions/122 + */ + protected bool $drawCircularModules = false; + + /** + * Specifies the radius of the modules when `QROptions::$drawCircularModules` is set to `true` + * + * (default: 0.45) + */ + protected float $circleRadius = 0.45; + + /** + * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true` + * + * (default: `[]`) + */ + protected array $keepAsSquare = []; + + /** + * Whether to connect the paths for the several module types to avoid weird glitches when using gradients etc. + * + * This option is exclusive to output classes that use the module collector `QROutputAbstract::collectModules()`, + * which converts the `$M_TYPE` of all modules to `QRMatrix::M_DATA` and `QRMatrix::M_DATA_DARK` respectively. + * + * Module types that should not be added to the connected path can be excluded via `QROptions::$excludeFromConnect`. + * + * Currentty used in `QREps` and `QRMarkupSVG`. + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules() + * @see \chillerlan\QRCode\QROptionsTrait::$excludeFromConnect + * @see https://github.com/chillerlan/php-qrcode/issues/57 + */ + protected bool $connectPaths = false; + + /** + * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true` + * + * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths + */ + protected array $excludeFromConnect = []; + + /** + * Module values map + * + * - `QRImagick`, `QRMarkupHTML`, `QRMarkupSVG`: #ABCDEF, cssname, rgb(), rgba()... + * - `QREps`, `QRFpdf`, `QRGdImage`: `[R, G, B]` // 0-255 + * - `QREps`: `[C, M, Y, K]` // 0-255 + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues() + */ + protected array $moduleValues = []; + + /** + * Toggles logo space creation + * + * @see \chillerlan\QRCode\QRCode::addMatrixModifications() + * @see \chillerlan\QRCode\Data\QRMatrix::setLogoSpace() + */ + protected bool $addLogoSpace = false; + + /** + * Width of the logo space + * + * if only `QROptions::$logoSpaceWidth` is given, the logo space is assumed a square of that size + */ + protected ?int $logoSpaceWidth = null; + + /** + * Height of the logo space + * + * if only `QROptions::$logoSpaceHeight` is given, the logo space is assumed a square of that size + */ + protected ?int $logoSpaceHeight = null; + + /** + * Optional horizontal start position of the logo space (top left corner) + */ + protected ?int $logoSpaceStartX = null; + + /** + * Optional vertical start position of the logo space (top left corner) + */ + protected ?int $logoSpaceStartY = null; + + + /* + * Common raster image settings (QRGdImage, QRImagick) + */ + + /** + * Pixel size of a QR code module + */ + protected int $scale = 5; + + /** + * Toggle transparency + * + * - `QRGdImage` and `QRImagick`: the given `QROptions::$transparencyColor` is set as transparent + * + * @see https://github.com/chillerlan/php-qrcode/discussions/121 + */ + protected bool $imageTransparent = false; + + /** + * Sets a transparency color for when `QROptions::$imageTransparent` is set to `true`. + * + * Defaults to `QROptions::$bgColor`. + * + * - `QRGdImage`: `[R, G, B]`, this color is set as transparent in `imagecolortransparent()` + * - `QRImagick`: `"color_str"`, this color is set in `Imagick::transparentPaintImage()` + * + * @see \imagecolortransparent() + * @see \Imagick::transparentPaintImage() + * + * @var mixed|null + */ + protected $transparencyColor = null; + + /** + * Compression quality + * + * The given value depends on the used output type: + * + * - `QRGdImageBMP`: `[0...1]` + * - `QRGdImageJPEG`: `[0...100]` + * - `QRGdImageWEBP`: `[0...9]` + * - `QRGdImagePNG`: `[0...100]` + * - `QRImagick`: `[0...100]` + * + * @see \imagebmp() + * @see \imagejpeg() + * @see \imagepng() + * @see \imagewebp() + * @see \Imagick::setImageCompressionQuality() + */ + protected int $quality = -1; + + /* + * QRGdImage settings + */ + + /** + * Toggles the usage of internal upscaling when `QROptions::$drawCircularModules` is set to `true` and + * `QROptions::$scale` is less than 20 + * + * @see \chillerlan\QRCode\Output\QRGdImage::createImage() + * @see https://github.com/chillerlan/php-qrcode/issues/23 + */ + protected bool $gdImageUseUpscale = true; + + /* + * QRImagick settings + */ + + /** + * Imagick output format + * + * @see \Imagick::setImageFormat() + * @see https://www.imagemagick.org/script/formats.php + */ + protected string $imagickFormat = 'png32'; + + + /* + * Common markup output settings (QRMarkupSVG, QRMarkupHTML) + */ + + /** + * A common css class + */ + protected string $cssClass = 'qrcode'; + + /* + * QRMarkupSVG settings + */ + + /** + * Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML + * + * `` + */ + protected bool $svgAddXmlHeader = true; + + /** + * Anything in the SVG `` tag + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs + */ + protected string $svgDefs = ''; + + /** + * Sets the value for the "preserveAspectRatio" on the `` element + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio + */ + protected string $svgPreserveAspectRatio = 'xMidYMid'; + + /** + * Whether to use the SVG `fill` attributes + * + * If set to `true` (default), the `fill` attribute will be set with the module value for the `` element's `$M_TYPE`. + * When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS. + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + */ + protected bool $svgUseFillAttributes = true; + + /* + * QRStringText settings + */ + + /** + * An optional line prefix, e.g. empty space to align the QR Code in a console + */ + protected string $textLineStart = ''; + + /* + * QRStringJSON settings + */ + + /** + * Sets the flags to use for the `json_encode()` call + * + * @see https://www.php.net/manual/json.constants.php + */ + protected int $jsonFlags = JSON_THROW_ON_ERROR; + + /** + * Whether to return matrix values in JSON as booleans or `$M_TYPE` integers + */ + protected bool $jsonAsBooleans = false; + + /* + * QRFpdf settings + */ + + /** + * Measurement unit for `FPDF` output: `pt`, `mm`, `cm`, `in` (default: `pt`) + * + * @see FPDF::__construct() + */ + protected string $fpdfMeasureUnit = 'pt'; + + + /* + * QR Code reader settings + */ + + /** + * Use Imagick (if available) when reading QR Codes + */ + protected bool $readerUseImagickIfAvailable = false; + + /** + * Grayscale the image before reading + */ + protected bool $readerGrayscale = false; + + /** + * Invert the colors of the image + */ + protected bool $readerInvertColors = false; + + /** + * Increase the contrast before reading + * + * note that applying contrast works different in GD and Imagick, so mileage may vary + */ + protected bool $readerIncreaseContrast = false; + + + /** + * clamp min/max version number + */ + protected function setMinMaxVersion(int $versionMin, int $versionMax):void{ + $min = max(1, min(40, $versionMin)); + $max = max(1, min(40, $versionMax)); + + $this->versionMin = min($min, $max); + $this->versionMax = max($min, $max); + } + + /** + * sets the minimum version number + */ + protected function set_versionMin(int $version):void{ + $this->setMinMaxVersion($version, $this->versionMax); + } + + /** + * sets the maximum version number + */ + protected function set_versionMax(int $version):void{ + $this->setMinMaxVersion($this->versionMin, $version); + } + + /** + * sets/clamps the version number + */ + protected function set_version(int $version):void{ + $this->version = ($version !== Version::AUTO) ? max(1, min(40, $version)) : Version::AUTO; + } + + /** + * sets/clamps the quiet zone size + */ + protected function set_quietzoneSize(int $quietzoneSize):void{ + $this->quietzoneSize = max(0, min($quietzoneSize, 75)); + } + + /** + * sets the FPDF measurement unit + * + * @codeCoverageIgnore + */ + protected function set_fpdfMeasureUnit(string $unit):void{ + $unit = strtolower($unit); + + if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){ + $this->fpdfMeasureUnit = $unit; + } + + // @todo throw or ignore silently? + } + + /** + * enables Imagick for the QR Code reader if the extension is available + */ + protected function set_readerUseImagickIfAvailable(bool $useImagickIfAvailable):void{ + $this->readerUseImagickIfAvailable = ($useImagickIfAvailable && extension_loaded('imagick')); + } + + /** + * clamp the logo space values between 0 and maximum length (177 modules at version 40) + */ + protected function clampLogoSpaceValue(?int $value):?int{ + + if($value === null){ + return null; + } + + return (int)max(0, min(177, $value)); + } + + /** + * clamp/set logo space width + */ + protected function set_logoSpaceWidth(?int $value):void{ + $this->logoSpaceWidth = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set logo space height + */ + protected function set_logoSpaceHeight(?int $value):void{ + $this->logoSpaceHeight = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set horizontal logo space start + */ + protected function set_logoSpaceStartX(?int $value):void{ + $this->logoSpaceStartX = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set vertical logo space start + */ + protected function set_logoSpaceStartY(?int $value):void{ + $this->logoSpaceStartY = $this->clampLogoSpaceValue($value); + } + + /** + * clamp/set SVG circle radius + */ + protected function set_circleRadius(float $circleRadius):void{ + $this->circleRadius = max(0.1, min(0.75, $circleRadius)); + } + + /* + * redirect calls of deprecated variables to new/renamed property + */ + + /** + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + */ + protected bool $imageBase64; + + /** + * redirect call to the new variable + * + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + * @codeCoverageIgnore + */ + protected function set_imageBase64(bool $imageBase64):void{ + $this->outputBase64 = $imageBase64; + } + + /** + * redirect call to the new variable + * + * @deprecated 5.0.0 use QROptions::$outputBase64 instead + * @see \chillerlan\QRCode\QROptions::$outputBase64 + * @codeCoverageIgnore + */ + protected function get_imageBase64():bool{ + return $this->outputBase64; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + */ + protected int $jpegQuality; + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function set_jpegQuality(int $jpegQuality):void{ + $this->quality = $jpegQuality; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function get_jpegQuality():int{ + return $this->quality; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + */ + protected int $pngCompression; + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function set_pngCompression(int $pngCompression):void{ + $this->quality = $pngCompression; + } + + /** + * @deprecated 5.0.0 use QROptions::$quality instead + * @see \chillerlan\QRCode\QROptions::$quality + * @codeCoverageIgnore + */ + protected function get_pngCompression():int{ + return $this->quality; + } + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + */ + protected array $imageTransparencyBG; + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + * @codeCoverageIgnore + */ + protected function set_imageTransparencyBG(?array $imageTransparencyBG):void{ + $this->transparencyColor = $imageTransparencyBG; + } + + /** + * @deprecated 5.0.0 use QROptions::$transparencyColor instead + * @see \chillerlan\QRCode\QROptions::$transparencyColor + * @codeCoverageIgnore + */ + protected function get_imageTransparencyBG():?array{ + return $this->transparencyColor; + } + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + */ + protected string $imagickBG; + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + * @codeCoverageIgnore + */ + protected function set_imagickBG(?string $imagickBG):void{ + $this->bgColor = $imagickBG; + } + + /** + * @deprecated 5.0.0 use QROptions::$bgColor instead + * @see \chillerlan\QRCode\QROptions::$bgColor + * @codeCoverageIgnore + */ + protected function get_imagickBG():?string{ + return $this->bgColor; + } + +} diff --git a/src/vendor/chillerlan/php-settings-container/LICENSE b/src/vendor/chillerlan/php-settings-container/LICENSE new file mode 100644 index 000000000..25d371fc3 --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Smiley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/chillerlan/php-settings-container/README.md b/src/vendor/chillerlan/php-settings-container/README.md new file mode 100644 index 000000000..32f606fbc --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/README.md @@ -0,0 +1,164 @@ +# chillerlan/php-settings-container + +A container class for settings objects - decouple configuration logic from your application! Not a DI container. +- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy. + +[![PHP Version Support][php-badge]][php] +[![version][packagist-badge]][packagist] +[![license][license-badge]][license] +[![Continuous Integration][gh-action-badge]][gh-action] +[![Coverage][coverage-badge]][coverage] +[![Codacy][codacy-badge]][codacy] +[![Packagist downloads][downloads-badge]][downloads] + +[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-settings-container?logo=php&color=8892BF +[php]: https://www.php.net/supported-versions.php +[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?logo=packagist +[packagist]: https://packagist.org/packages/chillerlan/php-settings-container +[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg +[license]: https://github.com/chillerlan/php-settings-container/blob/main/LICENSE +[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?logo=codecov +[coverage]: https://codecov.io/github/chillerlan/php-settings-container +[codacy-badge]: https://img.shields.io/codacy/grade/bd2467799e2943d2853ce3ebad5af490/main?logo=codacy +[codacy]: https://www.codacy.com/gh/chillerlan/php-settings-container/dashboard?branch=main +[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?logo=packagist +[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats +[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-settings-container/ci.yml?branch=main&logo=github +[gh-action]: https://github.com/chillerlan/php-settings-container/actions/workflows/ci.yml?query=branch%3Amain + +## Documentation + +### Installation +**requires [composer](https://getcomposer.org)** + +*composer.json* (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^3.0` - see [releases](https://github.com/chillerlan/php-settings-container/releases) for valid versions) +```json +{ + "require": { + "php": "^8.1", + "chillerlan/php-settings-container": "dev-main" + } +} +``` + +Profit! + +## Usage + +The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract`) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc. +It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait. + +### Simple usage +```php +class MyContainer extends SettingsContainerAbstract{ + protected string $foo; + protected string $bar; +} +``` + +```php +// use it just like a \stdClass (except the properties are fixed) +$container = new MyContainer; +$container->foo = 'what'; +$container->bar = 'foo'; + +// which is equivalent to +$container = new MyContainer(['bar' => 'foo', 'foo' => 'what']); +// ...or try +$container->fromJSON('{"foo": "what", "bar": "foo"}'); + + +// fetch all properties as array +$container->toArray(); // -> ['foo' => 'what', 'bar' => 'foo'] +// or JSON +$container->toJSON(); // -> {"foo": "what", "bar": "foo"} +// JSON via JsonSerializable +$json = json_encode($container); // -> {"foo": "what", "bar": "foo"} + +//non-existing properties will be ignored: +$container->nope = 'what'; + +var_dump($container->nope); // -> null +``` + +### Advanced usage +```php +// from library 1 +trait SomeOptions{ + protected string $foo; + protected string $what; + + // this method will be called in SettingsContainerAbstract::construct() + // after the properties have been set + protected function SomeOptions():void{ + // just some constructor stuff... + $this->foo = strtoupper($this->foo); + } + + /* + * special prefixed magic setters & getters + */ + + // this method will be called from __set() when property $what is set + protected function set_what(string $value):void{ + $this->what = md5($value); + } + + // this method is called on __get() for the property $what + protected function get_what():string{ + return 'hash: '.$this->what; + } +} + +// from library 2 +trait MoreOptions{ + protected string $bar = 'whatever'; // provide default values +} +``` + +```php +$commonOptions = [ + // SomeOptions + 'foo' => 'whatever', + // MoreOptions + 'bar' => 'nothing', +]; + +// now plug the several library options together to a single object +$container = new class ($commonOptions) extends SettingsContainerAbstract{ + use SomeOptions, MoreOptions; +}; + +var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value) +var_dump($container->bar); // -> nothing + +$container->what = 'some value'; +var_dump($container->what); // -> hash: 5946210c9e93ae37891dfe96c3e39614 (custom getter added "hash: ") +``` + +### API + +#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php) + +| method | return | info | +|--------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set | +| (protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait | +| `__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists | +| `__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists | +| `__isset(string $property)` | bool | | +| `__unset(string $property)` | void | | +| `__toString()` | string | a JSON string | +| `toArray()` | array | | +| `fromIterable(iterable $properties)` | `SettingsContainerInterface` | | +| `toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php) | +| `fromJSON(string $json)` | `SettingsContainerInterface` | | +| `jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface | +| `serialize()` | string | implements the [`Serializable`](https://www.php.net/manual/en/serializable.serialize.php) interface | +| `unserialize(string $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/serializable.unserialize.php) interface | +| `__serialize()` | array | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.serialize) interface | +| `__unserialize(array $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize) interface | + +## Disclaimer +This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works. +Also, this is not a dependency injection container. Stop using DI containers FFS. diff --git a/src/vendor/chillerlan/php-settings-container/composer.json b/src/vendor/chillerlan/php-settings-container/composer.json new file mode 100644 index 000000000..8d19b8db4 --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/composer.json @@ -0,0 +1,51 @@ +{ + "name": "chillerlan/php-settings-container", + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "license": "MIT", + "type": "library", + "minimum-stability": "stable", + "keywords": [ + "helper", "container", "settings", "configuration" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "require": { + "php": "^8.1", + "ext-json": "*" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "chillerlan\\SettingsTest\\": "tests/" + } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phan": "@php vendor/bin/phan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true + } +} diff --git a/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php new file mode 100644 index 000000000..b54ca909c --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php @@ -0,0 +1,240 @@ + + * @copyright 2018 Smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\Settings; + +use InvalidArgumentException, ReflectionClass, ReflectionProperty; +use function array_keys, get_object_vars, is_object, json_decode, + json_encode, method_exists, property_exists, serialize, unserialize; +use const JSON_THROW_ON_ERROR; + +abstract class SettingsContainerAbstract implements SettingsContainerInterface{ + + /** + * SettingsContainerAbstract constructor. + */ + public function __construct(iterable|null $properties = null){ + + if(!empty($properties)){ + $this->fromIterable($properties); + } + + $this->construct(); + } + + /** + * calls a method with trait name as replacement constructor for each used trait + * (remember pre-php5 classname constructors? yeah, basically this.) + */ + protected function construct():void{ + $traits = (new ReflectionClass($this))->getTraits(); + + foreach($traits as $trait){ + $method = $trait->getShortName(); + + if(method_exists($this, $method)){ + $this->{$method}(); + } + } + + } + + /** + * @inheritdoc + */ + public function __get(string $property):mixed{ + + if(!property_exists($this, $property) || $this->isPrivate($property)){ + return null; + } + + $method = 'get_'.$property; + + if(method_exists($this, $method)){ + return $this->{$method}(); + } + + return $this->{$property}; + } + + /** + * @inheritdoc + */ + public function __set(string $property, mixed $value):void{ + + if(!property_exists($this, $property) || $this->isPrivate($property)){ + return; + } + + $method = 'set_'.$property; + + if(method_exists($this, $method)){ + $this->{$method}($value); + + return; + } + + $this->{$property} = $value; + } + + /** + * @inheritdoc + */ + public function __isset(string $property):bool{ + return isset($this->{$property}) && !$this->isPrivate($property); + } + + /** + * @internal Checks if a property is private + */ + protected function isPrivate(string $property):bool{ + return (new ReflectionProperty($this, $property))->isPrivate(); + } + + /** + * @inheritdoc + */ + public function __unset(string $property):void{ + + if($this->__isset($property)){ + unset($this->{$property}); + } + + } + + /** + * @inheritdoc + */ + public function __toString():string{ + return $this->toJSON(); + } + + /** + * @inheritdoc + */ + public function toArray():array{ + $properties = []; + + foreach(array_keys(get_object_vars($this)) as $key){ + $properties[$key] = $this->__get($key); + } + + return $properties; + } + + /** + * @inheritdoc + */ + public function fromIterable(iterable $properties):static{ + + foreach($properties as $key => $value){ + $this->__set($key, $value); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function toJSON(int|null $jsonOptions = null):string{ + return json_encode($this, ($jsonOptions ?? 0)); + } + + /** + * @inheritdoc + */ + public function fromJSON(string $json):static{ + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + + return $this->fromIterable($data); + } + + /** + * @inheritdoc + */ + public function jsonSerialize():array{ + return $this->toArray(); + } + + /** + * Returns a serialized string representation of the object in its current state (except static/readonly properties) + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function serialize():string{ + return serialize($this); + } + + /** + * Restores the data (except static/readonly properties) from the given serialized object to the current instance + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function unserialize(string $data):void{ + $obj = unserialize($data); + + if($obj === false || !is_object($obj)){ + throw new InvalidArgumentException('The given serialized string is invalid'); + } + + $reflection = new ReflectionClass($obj); + + if(!$reflection->isInstance($this)){ + throw new InvalidArgumentException('The unserialized object does not match the class of this container'); + } + + $properties = $reflection->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)); + + foreach($properties as $reflectionProperty){ + $this->{$reflectionProperty->name} = $reflectionProperty->getValue($obj); + } + + } + + /** + * Returns a serialized string representation of the object in its current state (except static/readonly properties) + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function __serialize():array{ + + $properties = (new ReflectionClass($this)) + ->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY)) + ; + + $data = []; + + foreach($properties as $reflectionProperty){ + $data[$reflectionProperty->name] = $reflectionProperty->getValue($this); + } + + return $data; + } + + /** + * Restores the data from the given array to the current instance + * + * @inheritdoc + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function __unserialize(array $data):void{ + + foreach($data as $key => $value){ + $this->{$key} = $value; + } + + } + +} diff --git a/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php new file mode 100644 index 000000000..1c9c12a73 --- /dev/null +++ b/src/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php @@ -0,0 +1,78 @@ + + * @copyright 2018 Smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\Settings; + +use JsonSerializable, Serializable; + +/** + * a generic container with magic getter and setter + */ +interface SettingsContainerInterface extends JsonSerializable, Serializable{ + + /** + * Retrieve the value of $property + * + * @return mixed|null + */ + public function __get(string $property):mixed; + + /** + * Set $property to $value while avoiding private and non-existing properties + */ + public function __set(string $property, mixed $value):void; + + /** + * Checks if $property is set (aka. not null), excluding private properties + */ + public function __isset(string $property):bool; + + /** + * Unsets $property while avoiding private and non-existing properties + */ + public function __unset(string $property):void; + + /** + * @see \chillerlan\Settings\SettingsContainerInterface::toJSON() + */ + public function __toString():string; + + /** + * Returns an array representation of the settings object + * + * The values will be run through the magic __get(), which may also call custom getters. + */ + public function toArray():array; + + /** + * Sets properties from a given iterable + * + * The values will be run through the magic __set(), which may also call custom setters. + */ + public function fromIterable(iterable $properties):static; + + /** + * Returns a JSON representation of the settings object + * @see \json_encode() + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + */ + public function toJSON(int|null $jsonOptions = null):string; + + /** + * Sets properties from a given JSON string + * + * @throws \Exception + * @throws \JsonException + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + */ + public function fromJSON(string $json):static; + +} diff --git a/src/vendor/composer/ClassLoader.php b/src/vendor/composer/ClassLoader.php new file mode 100644 index 000000000..7824d8f7e --- /dev/null +++ b/src/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/src/vendor/composer/InstalledVersions.php b/src/vendor/composer/InstalledVersions.php new file mode 100644 index 000000000..51e734a77 --- /dev/null +++ b/src/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/LICENSE b/src/vendor/composer/LICENSE similarity index 87% rename from LICENSE rename to src/vendor/composer/LICENSE index 5285f420f..f27399a04 100644 --- a/LICENSE +++ b/src/vendor/composer/LICENSE @@ -1,13 +1,12 @@ -MIT License -Copyright (c) 2017 +Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,5 +16,6 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/composer/autoload_classmap.php b/src/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..0fb0a2c19 --- /dev/null +++ b/src/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/src/vendor/composer/autoload_namespaces.php b/src/vendor/composer/autoload_namespaces.php new file mode 100644 index 000000000..15a2ff3ad --- /dev/null +++ b/src/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/chillerlan/php-settings-container/src'), + 'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'), + 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php new file mode 100644 index 000000000..26f405476 --- /dev/null +++ b/src/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php new file mode 100644 index 000000000..61e9ce63a --- /dev/null +++ b/src/vendor/composer/autoload_static.php @@ -0,0 +1,49 @@ + + array ( + 'chillerlan\\Settings\\' => 20, + 'chillerlan\\QRCode\\' => 18, + ), + 'P' => + array ( + 'PHPMailer\\PHPMailer\\' => 20, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'chillerlan\\Settings\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src', + ), + 'chillerlan\\QRCode\\' => + array ( + 0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src', + ), + 'PHPMailer\\PHPMailer\\' => + array ( + 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitd2ab1ff203e9fa048bf2f3325f44d777::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitd2ab1ff203e9fa048bf2f3325f44d777::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitd2ab1ff203e9fa048bf2f3325f44d777::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json new file mode 100644 index 000000000..e7e6f3754 --- /dev/null +++ b/src/vendor/composer/installed.json @@ -0,0 +1,255 @@ +{ + "packages": [ + { + "name": "chillerlan/php-qrcode", + "version": "5.0.2", + "version_normalized": "5.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-qrcode.git", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/da5bdb82c8755f54de112b271b402aaa8df53269", + "reference": "da5bdb82c8755f54de112b271b402aaa8df53269", + "shasum": "" + }, + "require": { + "chillerlan/php-settings-container": "^2.1.4 || ^3.1", + "ext-mbstring": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "chillerlan/php-authenticator": "^4.1 || ^5.1", + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" + }, + "time": "2024-02-27T14:37:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase/qrcode-generator" + }, + { + "name": "ZXing Authors", + "homepage": "https://github.com/zxing/zxing" + }, + { + "name": "Ashot Khanamiryan", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "description": "A QR code generator and reader with a user friendly API. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "keywords": [ + "phpqrcode", + "qr", + "qr code", + "qr-reader", + "qrcode", + "qrcode-generator", + "qrcode-reader" + ], + "support": { + "docs": "https://php-qrcode.readthedocs.io", + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "install-path": "../chillerlan/php-qrcode" + }, + { + "name": "chillerlan/php-settings-container", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6", + "reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "time": "2024-03-02T20:07:15+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "description": "A container class for immutable settings objects. Not a DI container.", + "homepage": "https://github.com/chillerlan/php-settings-container", + "keywords": [ + "Settings", + "configuration", + "container", + "helper" + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "install-path": "../chillerlan/php-settings-container" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "version_normalized": "6.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "time": "2023-11-25T22:23:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "install-path": "../phpmailer/phpmailer" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php new file mode 100644 index 000000000..8b62ba23d --- /dev/null +++ b/src/vendor/composer/installed.php @@ -0,0 +1,50 @@ + array( + 'name' => 'zoeyvid/npmplus', + 'pretty_version' => 'v0.0.1-alpha', + 'version' => '0.0.1.0-alpha', + 'reference' => null, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'chillerlan/php-qrcode' => array( + 'pretty_version' => '5.0.2', + 'version' => '5.0.2.0', + 'reference' => 'da5bdb82c8755f54de112b271b402aaa8df53269', + 'type' => 'library', + 'install_path' => __DIR__ . '/../chillerlan/php-qrcode', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'chillerlan/php-settings-container' => array( + 'pretty_version' => '3.2.0', + 'version' => '3.2.0.0', + 'reference' => '8f93648fac8e6bacac8e00a8d325eba4950295e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../chillerlan/php-settings-container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpmailer/phpmailer' => array( + 'pretty_version' => 'v6.9.1', + 'version' => '6.9.1.0', + 'reference' => '039de174cd9c17a8389754d3b877a2ed22743e18', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmailer/phpmailer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'zoeyvid/npmplus' => array( + 'pretty_version' => 'v0.0.1-alpha', + 'version' => '0.0.1.0-alpha', + 'reference' => null, + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/src/vendor/composer/platform_check.php b/src/vendor/composer/platform_check.php new file mode 100644 index 000000000..4c3a5d68f --- /dev/null +++ b/src/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/src/vendor/phpmailer/phpmailer/.editorconfig b/src/vendor/phpmailer/phpmailer/.editorconfig new file mode 100644 index 000000000..a7c44ddb1 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/src/vendor/phpmailer/phpmailer/COMMITMENT b/src/vendor/phpmailer/phpmailer/COMMITMENT new file mode 100644 index 000000000..a687e0ddb --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/COMMITMENT @@ -0,0 +1,46 @@ +GPL Cooperation Commitment +Version 1.0 + +Before filing or continuing to prosecute any legal proceeding or claim +(other than a Defensive Action) arising from termination of a Covered +License, we commit to extend to the person or entity ('you') accused +of violating the Covered License the following provisions regarding +cure and reinstatement, taken from GPL version 3. As used here, the +term 'this License' refers to the specific Covered License being +enforced. + + However, if you cease all violation of this License, then your + license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly + and finally terminates your license, and (b) permanently, if the + copyright holder fails to notify you of the violation by some + reasonable means prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you + have received notice of violation of this License (for any work) + from that copyright holder, and you cure the violation prior to 30 + days after your receipt of the notice. + +We intend this Commitment to be irrevocable, and binding and +enforceable against us and assignees of or successors to our +copyrights. + +Definitions + +'Covered License' means the GNU General Public License, version 2 +(GPLv2), the GNU Lesser General Public License, version 2.1 +(LGPLv2.1), or the GNU Library General Public License, version 2 +(LGPLv2), all as published by the Free Software Foundation. + +'Defensive Action' means a legal proceeding or claim that We bring +against you in response to a prior proceeding or claim initiated by +you or your affiliate. + +'We' means each contributor to this repository as of the date of +inclusion of this file, including subsidiaries of a corporate +contributor. + +This work is available under a Creative Commons Attribution-ShareAlike +4.0 International license (https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/src/vendor/phpmailer/phpmailer/LICENSE b/src/vendor/phpmailer/phpmailer/LICENSE new file mode 100644 index 000000000..f166cc57b --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/src/vendor/phpmailer/phpmailer/README.md b/src/vendor/phpmailer/phpmailer/README.md new file mode 100644 index 000000000..e3e4ecff3 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/README.md @@ -0,0 +1,231 @@ +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://supportukrainenow.org/) + +![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png) + +# PHPMailer – A full-featured email creation and transfer class for PHP + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) +[![codecov.io](https://codecov.io/gh/PHPMailer/PHPMailer/branch/master/graph/badge.svg?token=iORZpwmYmM)](https://codecov.io/gh/PHPMailer/PHPMailer) +[![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) +[![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer/badge)](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer) + +## Features +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support – send without a local mail server +- Send emails with multiple To, CC, BCC, and Reply-to addresses +- Multipart/alternative emails for mail clients that do not read HTML email +- Add attachments, including inline +- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings +- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports +- Validates email addresses automatically +- Protects against header injection attacks +- Error messages in over 50 languages! +- DKIM and S/MIME signing support +- Compatible with PHP 5.5 and later, including PHP 8.2 +- Namespaced to prevent name clashes +- Much more! + +## Why you might need it +Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe! + +The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost. + +*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that +you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/) +, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc. + +## License +This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. + +## Installation & loading +PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: + +```json +"phpmailer/phpmailer": "^6.9.1" +``` + +or run + +```sh +composer require phpmailer/phpmailer +``` + +Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer. + +If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at +by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services. + +Alternatively, if you're not using Composer, you +can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually: + +```php +SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output + $mail->isSMTP(); //Send using SMTP + $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through + $mail->SMTPAuth = true; //Enable SMTP authentication + $mail->Username = 'user@example.com'; //SMTP username + $mail->Password = 'secret'; //SMTP password + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption + $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS` + + //Recipients + $mail->setFrom('from@example.com', 'Mailer'); + $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient + $mail->addAddress('ellen@example.com'); //Name is optional + $mail->addReplyTo('info@example.com', 'Information'); + $mail->addCC('cc@example.com'); + $mail->addBCC('bcc@example.com'); + + //Attachments + $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments + $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name + + //Content + $mail->isHTML(true); //Set email format to HTML + $mail->Subject = 'Here is the subject'; + $mail->Body = 'This is the HTML message body in bold!'; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + echo 'Message has been sent'; +} catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; +} +``` + +You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more. + +If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +//To load the French version +$mail->setLanguage('fr', '/optional/path/to/language/directory/'); +``` + +We welcome corrections and new languages – if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations. + +## Documentation +Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated. + +Examples of how to use PHPMailer for common scenarios can be found in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder. If you're looking for a good starting point, we recommend you start with [the Gmail example](https://github.com/PHPMailer/PHPMailer/tree/master/examples/gmail.phps). + +To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via [GitHub's zip file download](https://github.com/PHPMailer/PHPMailer/archive/master.zip), so you'll need to either clone the git repository or use the above links to get to the examples directly. + +Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). + +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. + +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). + +## Tests +[PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) + +If this isn't passing, is there something you can do to help? + +## Security +Please disclose any vulnerabilities found responsibly – report security issues to the maintainers privately. + +See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security). + +## Contributing +Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge cases, expanding test coverage, and updating translations. + +If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it. + +If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +```sh +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git +``` + +Please *don't* use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained. + +## Sponsorship +Development time and resources for PHPMailer are provided by [Smartmessages.net](https://info.smartmessages.net/), the world's only privacy-first email marketing system. + +Smartmessages.net privacy-first email marketing logo + +Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard cash 💰. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors – just click the "Sponsor" button [on the project page](https://github.com/PHPMailer/PHPMailer). If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme. + +## PHPMailer For Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial +support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact packages you +use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +## Changelog +See [changelog](changelog.md). + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013. +- PHPMailer moves to [the PHPMailer organisation](https://github.com/PHPMailer) on GitHub in 2013. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with GitHub Actions. +- Composer support. +- Public development. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits, and branches from the original SourceForge project. diff --git a/src/vendor/phpmailer/phpmailer/SECURITY.md b/src/vendor/phpmailer/phpmailer/SECURITY.md new file mode 100644 index 000000000..035a87f7b --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/SECURITY.md @@ -0,0 +1,37 @@ +# Security notices relating to PHPMailer + +Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately. + +PHPMailer 6.4.1 and earlier contain a vulnerability that can result in untrusted code being called (if such code is injected into the host project's scope by other means). If the `$patternselect` parameter to `validateAddress()` is set to `'php'` (the default, defined by `PHPMailer::$validator`), and the global namespace contains a function called `php`, it will be called in preference to the built-in validator of the same name. Mitigated in PHPMailer 6.5.0 by denying the use of simple strings as validator function names. Recorded as [CVE-2021-3603](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-3603). Reported by [Vikrant Singh Chauhan](mailto:vi@hackberry.xyz) via [huntr.dev](https://www.huntr.dev/). + +PHPMailer versions 6.4.1 and earlier contain a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to persuade the server to load a file from that UNC path, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows. +PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [Jilin Diting Information Technology Co., Ltd](https://listensec.com) via Tidelift. + +PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts. + +PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security. + +PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. + +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. + +PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. + +PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). + +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). + +PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. + +PHPMailer versions prior to 5.2.10 (released May 2015) are vulnerable to [CVE-2008-5619](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-5619), a remote code execution vulnerability in the bundled html2text library. This file was removed in 5.2.10, so if you are using a version prior to that and make use of the html2text function, it's vitally important that you upgrade and remove this file. + +PHPMailer versions prior to 2.0.7 and 2.2.1 are vulnerable to [CVE-2012-0796](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-0796), an email header injection attack. + +Joomla 1.6.0 uses PHPMailer in an unsafe way, allowing it to reveal local file paths, reported in [CVE-2011-3747](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3747). + +PHPMailer didn't sanitise the `$lang_path` parameter in `SetLanguage`. This wasn't a problem in itself, but some apps (PHPClassifieds, ATutor) also failed to sanitise user-provided parameters passed to it, permitting semi-arbitrary local file inclusion, reported in [CVE-2010-4914](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4914), [CVE-2007-2021](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2021) and [CVE-2006-5734](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2006-5734). + +PHPMailer 1.7.2 and earlier contained a possible DDoS vulnerability reported in [CVE-2005-1807](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2005-1807). + +PHPMailer 1.7 and earlier (June 2003) have a possible vulnerability in the `SendmailSend` method where shell commands may not be sanitised. Reported in [CVE-2007-3215](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-3215). + diff --git a/src/vendor/phpmailer/phpmailer/VERSION b/src/vendor/phpmailer/phpmailer/VERSION new file mode 100644 index 000000000..dc3829f5e --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/VERSION @@ -0,0 +1 @@ +6.9.1 diff --git a/src/vendor/phpmailer/phpmailer/composer.json b/src/vendor/phpmailer/phpmailer/composer.json new file mode 100644 index 000000000..fa170a0bb --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/composer.json @@ -0,0 +1,79 @@ +{ + "name": "phpmailer/phpmailer", + "type": "library", + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "require": { + "php": ">=5.5.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPMailer\\Test\\": "test/" + } + }, + "license": "LGPL-2.1-only", + "scripts": { + "check": "./vendor/bin/phpcs", + "test": "./vendor/bin/phpunit --no-coverage", + "coverage": "./vendor/bin/phpunit", + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build" + ] + } +} diff --git a/src/vendor/phpmailer/phpmailer/get_oauth_token.php b/src/vendor/phpmailer/phpmailer/get_oauth_token.php new file mode 100644 index 000000000..cda0445c6 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -0,0 +1,182 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * Get an OAuth2 token from an OAuth2 provider. + * * Install this script on your server so that it's accessible + * as [https/http]:////get_oauth_token.php + * e.g.: http://localhost/phpmailer/get_oauth_token.php + * * Ensure dependencies are installed with 'composer install' + * * Set up an app in your Google/Yahoo/Microsoft account + * * Set the script address as the app's redirect URL + * If no refresh token is obtained when running this file, + * revoke access to your app and run the script again. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Aliases for League Provider Classes + * Make sure you have added these to your composer.json and run `composer install` + * Plenty to choose from here: + * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + */ +//@see https://github.com/thephpleague/oauth2-google +use League\OAuth2\Client\Provider\Google; +//@see https://packagist.org/packages/hayageek/oauth2-yahoo +use Hayageek\OAuth2\Client\Provider\Yahoo; +//@see https://github.com/stevenmaguire/oauth2-microsoft +use Stevenmaguire\OAuth2\Client\Provider\Microsoft; +//@see https://github.com/greew/oauth2-azure-provider +use Greew\OAuth2\Client\Provider\Azure; + +if (!isset($_GET['code']) && !isset($_POST['provider'])) { + ?> + + +
+

Select Provider

+ +
+ +
+ +
+ +
+

Enter id and secret

+

These details are obtained by setting up an app in your provider's developer console. +

+

ClientId:

+

ClientSecret:

+

TenantID (only relevant for Azure):

+ +
+ + + $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'accessType' => 'offline' +]; + +$options = []; +$provider = null; + +switch ($providerName) { + case 'Google': + $provider = new Google($params); + $options = [ + 'scope' => [ + 'https://mail.google.com/' + ] + ]; + break; + case 'Yahoo': + $provider = new Yahoo($params); + break; + case 'Microsoft': + $provider = new Microsoft($params); + $options = [ + 'scope' => [ + 'wl.imap', + 'wl.offline_access' + ] + ]; + break; + case 'Azure': + $params['tenantId'] = $tenantId; + + $provider = new Azure($params); + $options = [ + 'scope' => [ + 'https://outlook.office.com/SMTP.Send', + 'offline_access' + ] + ]; + break; +} + +if (null === $provider) { + exit('Provider missing'); +} + +if (!isset($_GET['code'])) { + //If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl($options); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + //Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + unset($_SESSION['provider']); + exit('Invalid state'); +} else { + unset($_SESSION['provider']); + //Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken( + 'authorization_code', + [ + 'code' => $_GET['code'] + ] + ); + //Use this to interact with an API on the users behalf + //Use this to get a new access token if the old one expires + echo 'Refresh Token: ', $token->getRefreshToken(); +} diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php new file mode 100644 index 000000000..0b2a72d52 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php @@ -0,0 +1,26 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'خطأ SMTP : لا يمكن تأكيد الهوية.'; +$PHPMAILER_LANG['connect_host'] = 'خطأ SMTP: لا يمكن الاتصال بالخادم SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطأ SMTP: لم يتم قبول المعلومات .'; +$PHPMAILER_LANG['empty_message'] = 'نص الرسالة فارغ'; +$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: '; +$PHPMAILER_LANG['execute'] = 'لا يمكن تنفيذ : '; +$PHPMAILER_LANG['file_access'] = 'لا يمكن الوصول للملف: '; +$PHPMAILER_LANG['file_open'] = 'خطأ في الملف: لا يمكن فتحه: '; +$PHPMAILER_LANG['from_failed'] = 'خطأ على مستوى عنوان المرسل : '; +$PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة البريد.'; +$PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.'; +$PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : '; +$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.'; +$PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'لا يمكن تعيين أو إعادة تعيين متغير: '; +$PHPMAILER_LANG['extension_missing'] = 'الإضافة غير موجودة: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php new file mode 100644 index 000000000..327dfbafa --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি'; +$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই'; +$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।'; +$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: '; +$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: '; +$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: '; +$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম'; +$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: '; +$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: '; +$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php new file mode 100644 index 000000000..552167ef6 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Nije moguće spojiti se sa SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php new file mode 100644 index 000000000..9e92ddaaf --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Памылка SMTP: памылка ідэнтыфікацыі.'; +$PHPMAILER_LANG['connect_host'] = 'Памылка SMTP: нельга ўстанавіць сувязь з SMTP-серверам.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Памылка SMTP: звесткі непрынятыя.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое паведамленне.'; +$PHPMAILER_LANG['encoding'] = 'Невядомая кадыроўка тэксту: '; +$PHPMAILER_LANG['execute'] = 'Нельга выканаць каманду: '; +$PHPMAILER_LANG['file_access'] = 'Няма доступу да файла: '; +$PHPMAILER_LANG['file_open'] = 'Нельга адкрыць файл: '; +$PHPMAILER_LANG['from_failed'] = 'Няправільны адрас адпраўніка: '; +$PHPMAILER_LANG['instantiate'] = 'Нельга прымяніць функцыю mail().'; +$PHPMAILER_LANG['invalid_address'] = 'Нельга даслаць паведамленне, няправільны email атрымальніка: '; +$PHPMAILER_LANG['provide_address'] = 'Запоўніце, калі ласка, правільны email атрымальніка.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - паштовы сервер не падтрымліваецца.'; +$PHPMAILER_LANG['recipients_failed'] = 'Памылка SMTP: няправільныя атрымальнікі: '; +$PHPMAILER_LANG['signing'] = 'Памылка подпісу паведамлення: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Памылка сувязі з SMTP-серверам.'; +$PHPMAILER_LANG['smtp_error'] = 'Памылка SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Нельга ўстанавіць або перамяніць значэнне пераменнай: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php new file mode 100644 index 000000000..c41f675df --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: Не може да се удостовери пред сървъра.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: Не може да се свърже с SMTP хоста.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: данните не са приети.'; +$PHPMAILER_LANG['empty_message'] = 'Съдържанието на съобщението е празно'; +$PHPMAILER_LANG['encoding'] = 'Неизвестно кодиране: '; +$PHPMAILER_LANG['execute'] = 'Не може да се изпълни: '; +$PHPMAILER_LANG['file_access'] = 'Няма достъп до файл: '; +$PHPMAILER_LANG['file_open'] = 'Файлова грешка: Не може да се отвори файл: '; +$PHPMAILER_LANG['from_failed'] = 'Следните адреси за подател са невалидни: '; +$PHPMAILER_LANG['instantiate'] = 'Не може да се инстанцира функцията mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Невалиден адрес: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' - пощенски сървър не се поддържа.'; +$PHPMAILER_LANG['provide_address'] = 'Трябва да предоставите поне един email адрес за получател.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: Следните адреси за Получател са невалидни: '; +$PHPMAILER_LANG['signing'] = 'Грешка при подписване: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP провален connect().'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP сървърна грешка: '; +$PHPMAILER_LANG['variable_set'] = 'Не може да се установи или възстанови променлива: '; +$PHPMAILER_LANG['extension_missing'] = 'Липсва разширение: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php new file mode 100644 index 000000000..473651080 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷'; +$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷'; +$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।'; +$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: '; +$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:'; +$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷'; +$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:'; +$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: '; +$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php new file mode 100644 index 000000000..34684855a --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: No s’ha pogut autenticar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: No es pot connectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Dades no acceptades.'; +$PHPMAILER_LANG['empty_message'] = 'El cos del missatge està buit.'; +$PHPMAILER_LANG['encoding'] = 'Codificació desconeguda: '; +$PHPMAILER_LANG['execute'] = 'No es pot executar: '; +$PHPMAILER_LANG['file_access'] = 'No es pot accedir a l’arxiu: '; +$PHPMAILER_LANG['file_open'] = 'Error d’Arxiu: No es pot obrir l’arxiu: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) següent(s) adreces de remitent han fallat: '; +$PHPMAILER_LANG['instantiate'] = 'No s’ha pogut crear una instància de la funció Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Adreça d’email invalida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no està suportat'; +$PHPMAILER_LANG['provide_address'] = 'S’ha de proveir almenys una adreça d’email com a destinatari.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Els següents destinataris han fallat: '; +$PHPMAILER_LANG['signing'] = 'Error al signar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ha fallat el SMTP Connect().'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No s’ha pogut establir o restablir la variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php new file mode 100644 index 000000000..e770a1a26 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php @@ -0,0 +1,28 @@ + + * Rewrite and extension of the work by Mikael Stokkebro + * + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.'; +$PHPMAILER_LANG['buggy_php'] = 'Din version af PHP er berørt af en fejl, som gør at dine beskeder muligvis vises forkert. For at rette dette kan du skifte til SMTP, slå mail.add_x_header headeren i din php.ini fil fra, skifte til MacOS eller Linux eller opgradere din version af PHP til 7.0.17+ eller 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.'; +$PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold'; +$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: '; +$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; +$PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: '; +$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; +$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; +$PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.'; +$PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: '; +$PHPMAILER_LANG['invalid_header'] = 'Ugyldig header navn eller værdi'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vært: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; +$PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere fejlede: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfejl: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kode: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Yderligere SMTP info: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalje: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: '; +$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php new file mode 100644 index 000000000..e7e59d2b6 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php @@ -0,0 +1,28 @@ + + * @author Crystopher Glodzienski Cardoso + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; +$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; +$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; +$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; +$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; +$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; +$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php new file mode 100644 index 000000000..93addc9e3 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Viga: Autoriseerimise viga.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Viga: Ei õnnestunud luua ühendust SMTP serveriga.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Viga: Vigased andmed.'; +$PHPMAILER_LANG['empty_message'] = 'Tühi kirja sisu'; +$PHPMAILER_LANG["encoding"] = 'Tundmatu kodeering: '; +$PHPMAILER_LANG['execute'] = 'Tegevus ebaõnnestus: '; +$PHPMAILER_LANG['file_access'] = 'Pole piisavalt õiguseid järgneva faili avamiseks: '; +$PHPMAILER_LANG['file_open'] = 'Faili Viga: Faili avamine ebaõnnestus: '; +$PHPMAILER_LANG['from_failed'] = 'Järgnev saatja e-posti aadress on vigane: '; +$PHPMAILER_LANG['instantiate'] = 'mail funktiooni käivitamine ebaõnnestus.'; +$PHPMAILER_LANG['invalid_address'] = 'Saatmine peatatud, e-posti address vigane: '; +$PHPMAILER_LANG['provide_address'] = 'Te peate määrama vähemalt ühe saaja e-posti aadressi.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' maileri tugi puudub.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Viga: Järgnevate saajate e-posti aadressid on vigased: '; +$PHPMAILER_LANG["signing"] = 'Viga allkirjastamisel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() ebaõnnestus.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serveri viga: '; +$PHPMAILER_LANG['variable_set'] = 'Ei õnnestunud määrata või lähtestada muutujat: '; +$PHPMAILER_LANG['extension_missing'] = 'Nõutud laiendus on puudu: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php new file mode 100644 index 000000000..295a47f95 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php @@ -0,0 +1,28 @@ + + * @author Mohammad Hossein Mojtahedi + */ + +$PHPMAILER_LANG['authenticate'] = 'خطای SMTP: احراز هویت با شکست مواجه شد.'; +$PHPMAILER_LANG['connect_host'] = 'خطای SMTP: اتصال به سرور SMTP برقرار نشد.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطای SMTP: داده‌ها نا‌درست هستند.'; +$PHPMAILER_LANG['empty_message'] = 'بخش متن پیام خالی است.'; +$PHPMAILER_LANG['encoding'] = 'کد‌گذاری نا‌شناخته: '; +$PHPMAILER_LANG['execute'] = 'امکان اجرا وجود ندارد: '; +$PHPMAILER_LANG['file_access'] = 'امکان دسترسی به فایل وجود ندارد: '; +$PHPMAILER_LANG['file_open'] = 'خطای File: امکان بازکردن فایل وجود ندارد: '; +$PHPMAILER_LANG['from_failed'] = 'آدرس فرستنده اشتباه است: '; +$PHPMAILER_LANG['instantiate'] = 'امکان معرفی تابع ایمیل وجود ندارد.'; +$PHPMAILER_LANG['invalid_address'] = 'آدرس ایمیل معتبر نیست: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer پشتیبانی نمی‌شود.'; +$PHPMAILER_LANG['provide_address'] = 'باید حداقل یک آدرس گیرنده وارد کنید.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطای SMTP: ارسال به آدرس گیرنده با خطا مواجه شد: '; +$PHPMAILER_LANG['signing'] = 'خطا در امضا: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'خطا در اتصال به SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'خطا در SMTP Server: '; +$PHPMAILER_LANG['variable_set'] = 'امکان ارسال یا ارسال مجدد متغیر‌ها وجود ندارد: '; +$PHPMAILER_LANG['extension_missing'] = 'افزونه موجود نیست: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php new file mode 100644 index 000000000..6d1e63739 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP feilur: Kundi ikki góðkenna.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP feilur: Kundi ikki knýta samband við SMTP vert.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP feilur: Data ikki góðkent.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Ókend encoding: '; +$PHPMAILER_LANG['execute'] = 'Kundi ikki útføra: '; +$PHPMAILER_LANG['file_access'] = 'Kundi ikki tilganga fílu: '; +$PHPMAILER_LANG['file_open'] = 'Fílu feilur: Kundi ikki opna fílu: '; +$PHPMAILER_LANG['from_failed'] = 'fylgjandi Frá/From adressa miseydnaðist: '; +$PHPMAILER_LANG['instantiate'] = 'Kuni ikki instantiera mail funktión.'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' er ikki supporterað.'; +$PHPMAILER_LANG['provide_address'] = 'Tú skal uppgeva minst móttakara-emailadressu(r).'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feilur: Fylgjandi móttakarar miseydnaðust: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php new file mode 100644 index 000000000..0d367fcf8 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -0,0 +1,37 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Non puido ser autentificado.'; +$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Non puido conectar co servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Datos non aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'Corpo da mensaxe vacía'; +$PHPMAILER_LANG['encoding'] = 'Codificación descoñecida: '; +$PHPMAILER_LANG['execute'] = 'Non puido ser executado: '; +$PHPMAILER_LANG['file_access'] = 'Nob puido acceder ó arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: No puido abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'A(s) seguinte(s) dirección(s) de remitente(s) deron erro: '; +$PHPMAILER_LANG['instantiate'] = 'Non puido crear unha instancia da función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Non puido envia-lo correo: dirección de email inválida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer non está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe engadir polo menos unha dirección de email coma destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Os seguintes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Erro ó firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Non puidemos axustar ou reaxustar a variábel: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php new file mode 100644 index 000000000..b123aa5fc --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'שגיאת SMTP: פעולת האימות נכשלה.'; +$PHPMAILER_LANG['connect_host'] = 'שגיאת SMTP: לא הצלחתי להתחבר לשרת SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'שגיאת SMTP: מידע לא התקבל.'; +$PHPMAILER_LANG['empty_message'] = 'גוף ההודעה ריק'; +$PHPMAILER_LANG['invalid_address'] = 'כתובת שגויה: '; +$PHPMAILER_LANG['encoding'] = 'קידוד לא מוכר: '; +$PHPMAILER_LANG['execute'] = 'לא הצלחתי להפעיל את: '; +$PHPMAILER_LANG['file_access'] = 'לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['file_open'] = 'שגיאת קובץ: לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['from_failed'] = 'כתובות הנמענים הבאות נכשלו: '; +$PHPMAILER_LANG['instantiate'] = 'לא הצלחתי להפעיל את פונקציית המייל.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' אינה נתמכת.'; +$PHPMAILER_LANG['provide_address'] = 'חובה לספק לפחות כתובת אחת של מקבל המייל.'; +$PHPMAILER_LANG['recipients_failed'] = 'שגיאת SMTP: הנמענים הבאים נכשלו: '; +$PHPMAILER_LANG['signing'] = 'שגיאת חתימה: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +$PHPMAILER_LANG['smtp_error'] = 'שגיאת שרת SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'לא ניתן לקבוע או לשנות את המשתנה: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php new file mode 100644 index 000000000..d2856e057 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php @@ -0,0 +1,35 @@ + + * Rewrite and extension of the work by Jayanti Suthar + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। '; +$PHPMAILER_LANG['buggy_php'] = 'PHP का आपका संस्करण एक बग से प्रभावित है जिसके परिणामस्वरूप संदेश दूषित हो सकते हैं. इसे ठीक करने हेतु, भेजने के लिए SMTP का उपयोग करे, अपने php.ini में mail.add_x_header विकल्प को अक्षम करें, MacOS या Linux पर जाए, या अपने PHP संस्करण को 7.0.17+ या 7.1.3+ बदले.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। '; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। '; +$PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। '; +$PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। '; +$PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। '; +$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; +$PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। '; +$PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। '; +$PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। '; +$PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।'; +$PHPMAILER_LANG['invalid_address'] = 'पता गलत है। '; +$PHPMAILER_LANG['invalid_header'] = 'अमान्य हेडर नाम या मान'; +$PHPMAILER_LANG['invalid_hostentry'] = 'अमान्य hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'अमान्य होस्ट: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। '; +$PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। '; +$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP कोड: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'अतिरिक्त SMTP जानकारी: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। '; +$PHPMAILER_LANG['smtp_detail'] = 'विवरण: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। '; +$PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php new file mode 100644 index 000000000..cacb6c37e --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela autentikacija.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne mogu se spojiti na SMTP poslužitelj.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznati encoding: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje s navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definirajte barem jednu adresu primatelja.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP poslužitelj nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP poslužitelja: '; +$PHPMAILER_LANG['variable_set'] = 'Ne mogu postaviti varijablu niti ju vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php new file mode 100644 index 000000000..e6b58b0db --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP -ի սխալ: չհաջողվեց ստուգել իսկությունը.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP -ի սխալ: չհաջողվեց կապ հաստատել SMTP սերվերի հետ.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP -ի սխալ: տվյալները ընդունված չեն.'; +$PHPMAILER_LANG['empty_message'] = 'Հաղորդագրությունը դատարկ է'; +$PHPMAILER_LANG['encoding'] = 'Կոդավորման անհայտ տեսակ: '; +$PHPMAILER_LANG['execute'] = 'Չհաջողվեց իրականացնել հրամանը: '; +$PHPMAILER_LANG['file_access'] = 'Ֆայլը հասանելի չէ: '; +$PHPMAILER_LANG['file_open'] = 'Ֆայլի սխալ: ֆայլը չհաջողվեց բացել: '; +$PHPMAILER_LANG['from_failed'] = 'Ուղարկողի հետևյալ հասցեն սխալ է: '; +$PHPMAILER_LANG['instantiate'] = 'Հնարավոր չէ կանչել mail ֆունկցիան.'; +$PHPMAILER_LANG['invalid_address'] = 'Հասցեն սխալ է: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' փոստային սերվերի հետ չի աշխատում.'; +$PHPMAILER_LANG['provide_address'] = 'Անհրաժեշտ է տրամադրել գոնե մեկ ստացողի e-mail հասցե.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP -ի սխալ: չի հաջողվել ուղարկել հետևյալ ստացողների հասցեներին: '; +$PHPMAILER_LANG['signing'] = 'Ստորագրման սխալ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP -ի connect() ֆունկցիան չի հաջողվել'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP սերվերի սխալ: '; +$PHPMAILER_LANG['variable_set'] = 'Չի հաջողվում ստեղծել կամ վերափոխել փոփոխականը: '; +$PHPMAILER_LANG['extension_missing'] = 'Հավելվածը բացակայում է: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php new file mode 100644 index 000000000..212a11f13 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php @@ -0,0 +1,31 @@ + + * @author @januridp + * @author Ian Mustafa + */ + +$PHPMAILER_LANG['authenticate'] = 'Kesalahan SMTP: Tidak dapat mengotentikasi.'; +$PHPMAILER_LANG['connect_host'] = 'Kesalahan SMTP: Tidak dapat terhubung ke host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Kesalahan SMTP: Data tidak diterima.'; +$PHPMAILER_LANG['empty_message'] = 'Isi pesan kosong'; +$PHPMAILER_LANG['encoding'] = 'Pengkodean karakter tidak dikenali: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat menjalankan proses: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses berkas: '; +$PHPMAILER_LANG['file_open'] = 'Kesalahan Berkas: Berkas tidak dapat dibuka: '; +$PHPMAILER_LANG['from_failed'] = 'Alamat pengirim berikut mengakibatkan kesalahan: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat menginisialisasi fungsi surel.'; +$PHPMAILER_LANG['invalid_address'] = 'Gagal terkirim, alamat surel tidak sesuai: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Gagal terkirim, entri host tidak sesuai: '; +$PHPMAILER_LANG['invalid_host'] = 'Gagal terkirim, host tidak sesuai: '; +$PHPMAILER_LANG['provide_address'] = 'Harus tersedia minimal satu alamat tujuan'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tidak didukung'; +$PHPMAILER_LANG['recipients_failed'] = 'Kesalahan SMTP: Alamat tujuan berikut menyebabkan kesalahan: '; +$PHPMAILER_LANG['signing'] = 'Kesalahan dalam penandatangan SSL: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Kesalahan pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak dapat mengatur atau mengatur ulang variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Ekstensi PHP tidak tersedia: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php new file mode 100644 index 000000000..08a6b7333 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php @@ -0,0 +1,28 @@ + + * @author Stefano Sabatini + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dati non accettati dal server.'; +$PHPMAILER_LANG['empty_message'] = 'Il corpo del messaggio è vuoto'; +$PHPMAILER_LANG['encoding'] = 'Codifica dei caratteri sconosciuta: '; +$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: '; +$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: '; +$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: '; +$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail'; +$PHPMAILER_LANG['invalid_address'] = 'Impossibile inviare, l\'indirizzo email non è valido: '; +$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente'; +$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato un errore: '; +$PHPMAILER_LANG['signing'] = 'Errore nella firma: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallita.'; +$PHPMAILER_LANG['smtp_error'] = 'Errore del server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Impossibile impostare o resettare la variabile: '; +$PHPMAILER_LANG['extension_missing'] = 'Estensione mancante: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php new file mode 100644 index 000000000..c76f5264c --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -0,0 +1,29 @@ + + * @author Yoshi Sakai + * @author Arisophy + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; +$PHPMAILER_LANG['empty_message'] = 'メール本文が空です。'; +$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; +$PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; +$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; +$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; +$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; +$PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: '; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; +$PHPMAILER_LANG['signing'] = '署名エラー: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: '; +$PHPMAILER_LANG['variable_set'] = '変数が存在しません: '; +$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php new file mode 100644 index 000000000..51fe403b4 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP შეცდომა: ავტორიზაცია შეუძლებელია.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP შეცდომა: SMTP სერვერთან დაკავშირება შეუძლებელია.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP შეცდომა: მონაცემები არ იქნა მიღებული.'; +$PHPMAILER_LANG['encoding'] = 'კოდირების უცნობი ტიპი: '; +$PHPMAILER_LANG['execute'] = 'შეუძლებელია შემდეგი ბრძანების შესრულება: '; +$PHPMAILER_LANG['file_access'] = 'შეუძლებელია წვდომა ფაილთან: '; +$PHPMAILER_LANG['file_open'] = 'ფაილური სისტემის შეცდომა: არ იხსნება ფაილი: '; +$PHPMAILER_LANG['from_failed'] = 'გამგზავნის არასწორი მისამართი: '; +$PHPMAILER_LANG['instantiate'] = 'mail ფუნქციის გაშვება ვერ ხერხდება.'; +$PHPMAILER_LANG['provide_address'] = 'გთხოვთ მიუთითოთ ერთი ადრესატის e-mail მისამართი მაინც.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - საფოსტო სერვერის მხარდაჭერა არ არის.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP შეცდომა: შემდეგ მისამართებზე გაგზავნა ვერ მოხერხდა: '; +$PHPMAILER_LANG['empty_message'] = 'შეტყობინება ცარიელია'; +$PHPMAILER_LANG['invalid_address'] = 'არ გაიგზავნა, e-mail მისამართის არასწორი ფორმატი: '; +$PHPMAILER_LANG['signing'] = 'ხელმოწერის შეცდომა: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'შეცდომა SMTP სერვერთან დაკავშირებისას'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP სერვერის შეცდომა: '; +$PHPMAILER_LANG['variable_set'] = 'შეუძლებელია შემდეგი ცვლადის შექმნა ან შეცვლა: '; +$PHPMAILER_LANG['extension_missing'] = 'ბიბლიოთეკა არ არსებობს: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php new file mode 100644 index 000000000..8c97dd947 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 오류: 인증할 수 없습니다.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 오류: SMTP 호스트에 접속할 수 없습니다.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 오류: 데이터가 받아들여지지 않았습니다.'; +$PHPMAILER_LANG['empty_message'] = '메세지 내용이 없습니다'; +$PHPMAILER_LANG['encoding'] = '알 수 없는 인코딩: '; +$PHPMAILER_LANG['execute'] = '실행 불가: '; +$PHPMAILER_LANG['file_access'] = '파일 접근 불가: '; +$PHPMAILER_LANG['file_open'] = '파일 오류: 파일을 열 수 없습니다: '; +$PHPMAILER_LANG['from_failed'] = '다음 From 주소에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['instantiate'] = 'mail 함수를 인스턴스화할 수 없습니다'; +$PHPMAILER_LANG['invalid_address'] = '잘못된 주소: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' 메일러는 지원되지 않습니다.'; +$PHPMAILER_LANG['provide_address'] = '적어도 한 개 이상의 수신자 메일 주소를 제공해야 합니다.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 오류: 다음 수신자에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['signing'] = '서명 오류: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 연결을 실패하였습니다.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 서버 오류: '; +$PHPMAILER_LANG['variable_set'] = '변수 설정 및 초기화 불가: '; +$PHPMAILER_LANG['extension_missing'] = '확장자 없음: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php new file mode 100644 index 000000000..4f115b1c5 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP klaida: autentifikacija nepavyko.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP klaida: nepavyksta prisijungti prie SMTP stoties.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP klaida: duomenys nepriimti.'; +$PHPMAILER_LANG['empty_message'] = 'Laiško turinys tuščias'; +$PHPMAILER_LANG['encoding'] = 'Neatpažinta koduotė: '; +$PHPMAILER_LANG['execute'] = 'Nepavyko įvykdyti komandos: '; +$PHPMAILER_LANG['file_access'] = 'Byla nepasiekiama: '; +$PHPMAILER_LANG['file_open'] = 'Bylos klaida: Nepavyksta atidaryti: '; +$PHPMAILER_LANG['from_failed'] = 'Neteisingas siuntėjo adresas: '; +$PHPMAILER_LANG['instantiate'] = 'Nepavyko paleisti mail funkcijos.'; +$PHPMAILER_LANG['invalid_address'] = 'Neteisingas adresas: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' pašto stotis nepalaikoma.'; +$PHPMAILER_LANG['provide_address'] = 'Nurodykite bent vieną gavėjo adresą.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP klaida: nepavyko išsiųsti šiems gavėjams: '; +$PHPMAILER_LANG['signing'] = 'Prisijungimo klaida: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP susijungimo klaida'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP stoties klaida: '; +$PHPMAILER_LANG['variable_set'] = 'Nepavyko priskirti reikšmės kintamajam: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php new file mode 100644 index 000000000..679b18cf9 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP kļūda: Autorizācija neizdevās.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Kļūda: Nevar izveidot savienojumu ar SMTP serveri.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Kļūda: Nepieņem informāciju.'; +$PHPMAILER_LANG['empty_message'] = 'Ziņojuma teksts ir tukšs'; +$PHPMAILER_LANG['encoding'] = 'Neatpazīts kodējums: '; +$PHPMAILER_LANG['execute'] = 'Neizdevās izpildīt komandu: '; +$PHPMAILER_LANG['file_access'] = 'Fails nav pieejams: '; +$PHPMAILER_LANG['file_open'] = 'Faila kļūda: Nevar atvērt failu: '; +$PHPMAILER_LANG['from_failed'] = 'Nepareiza sūtītāja adrese: '; +$PHPMAILER_LANG['instantiate'] = 'Nevar palaist sūtīšanas funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Nepareiza adrese: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' sūtītājs netiek atbalstīts.'; +$PHPMAILER_LANG['provide_address'] = 'Lūdzu, norādiet vismaz vienu adresātu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP kļūda: neizdevās nosūtīt šādiem saņēmējiem: '; +$PHPMAILER_LANG['signing'] = 'Autorizācijas kļūda: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP savienojuma kļūda'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP servera kļūda: '; +$PHPMAILER_LANG['variable_set'] = 'Nevar piešķirt mainīgā vērtību: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php new file mode 100644 index 000000000..8a94f6a04 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Hadisoana SMTP: Tsy nahomby ny fanamarinana.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Tsy afaka mampifandray amin\'ny mpampiantrano SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP diso: tsy voarakitra ny angona.'; +$PHPMAILER_LANG['empty_message'] = 'Tsy misy ny votoaty mailaka.'; +$PHPMAILER_LANG['encoding'] = 'Tsy fantatra encoding: '; +$PHPMAILER_LANG['execute'] = 'Tsy afaka manatanteraka ity baiko manaraka ity: '; +$PHPMAILER_LANG['file_access'] = 'Tsy nahomby ny fidirana amin\'ity rakitra ity: '; +$PHPMAILER_LANG['file_open'] = 'Hadisoana diso: Tsy afaka nanokatra ity file manaraka ity: '; +$PHPMAILER_LANG['from_failed'] = 'Ny adiresy iraka manaraka dia diso: '; +$PHPMAILER_LANG['instantiate'] = 'Tsy afaka nanomboka ny hetsika mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Tsy mety ny adiresy: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tsy manohana.'; +$PHPMAILER_LANG['provide_address'] = 'Alefaso azafady iray adiresy iray farafahakeliny.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Tsy mety ireo mpanaraka ireto: '; +$PHPMAILER_LANG['signing'] = 'Error nandritra ny sonia:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Tsy nahomby ny fifandraisana tamin\'ny server SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'Fahadisoana tamin\'ny server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tsy azo atao ny mametraka na mamerina ny variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Tsy hita ny ampahany: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php new file mode 100644 index 000000000..04d262c72 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Ralat SMTP: Tidak dapat pengesahan.'; +$PHPMAILER_LANG['connect_host'] = 'Ralat SMTP: Tidak dapat menghubungi hos pelayan SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ralat SMTP: Data tidak diterima oleh pelayan.'; +$PHPMAILER_LANG['empty_message'] = 'Tiada isi untuk mesej'; +$PHPMAILER_LANG['encoding'] = 'Pengekodan tidak diketahui: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat melaksanakan: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses fail: '; +$PHPMAILER_LANG['file_open'] = 'Ralat Fail: Tidak dapat membuka fail: '; +$PHPMAILER_LANG['from_failed'] = 'Berikut merupakan ralat dari alamat e-mel: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat memberi contoh fungsi e-mel.'; +$PHPMAILER_LANG['invalid_address'] = 'Alamat emel tidak sah: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' jenis penghantar emel tidak disokong.'; +$PHPMAILER_LANG['provide_address'] = 'Anda perlu menyediakan sekurang-kurangnya satu alamat e-mel penerima.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ralat SMTP: Penerima e-mel berikut telah gagal: '; +$PHPMAILER_LANG['signing'] = 'Ralat pada tanda tangan: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() telah gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Ralat pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak boleh menetapkan atau menetapkan semula pembolehubah: '; +$PHPMAILER_LANG['extension_missing'] = 'Sambungan hilang: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php new file mode 100644 index 000000000..c9621a164 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.'; +$PHPMAILER_LANG['buggy_php'] = 'PHP versie gededecteerd die onderhavig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de mail.add_x_header optie in uw php.ini file uit, gebruik MacOS of Linux, of pas de gebruikte PHP versie aan naar versie 7.0.17+ or 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.'; +$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg'; +$PHPMAILER_LANG['encoding'] = 'Onbekende codering: '; +$PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: '; +$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: '; +$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: '; +$PHPMAILER_LANG['from_failed'] = 'Het volgende afzendersadres is mislukt: '; +$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.'; +$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: '; +$PHPMAILER_LANG['invalid_header'] = 'Ongeldige header naam of waarde'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.'; +$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: '; +$PHPMAILER_LANG['signing'] = 'Signeerfout: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP code: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP informatie: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Verbinding mislukt.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detail: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: '; +$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of resetten: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php new file mode 100644 index 000000000..cb7b2c210 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.'; +$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: '; +$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: '; +$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: '; +$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php new file mode 100644 index 000000000..5239865a6 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php @@ -0,0 +1,38 @@ + + * @author Lucas Guimarães + * @author Phelipe Alves + * @author Fabio Beneditto + * @author Geidson Benício Coelho + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.'; +$PHPMAILER_LANG['buggy_php'] = 'Sua versão do PHP é afetada por um bug que por resultar em messagens corrompidas. Para corrigir, mude para enviar usando SMTP, desative a opção mail.add_x_header em seu php.ini, mude para MacOS ou Linux, ou atualize seu PHP para versão 7.0.17+ ou 7.1.3+ '; +$PHPMAILER_LANG['connect_host'] = 'Erro de SMTP: Não foi possível conectar ao servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro de SMTP: Dados rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'Mensagem vazia'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão não existe: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor de cabeçalho inválido'; +$PHPMAILER_LANG['invalid_hostentry'] = 'hostentry inválido: '; +$PHPMAILER_LANG['invalid_host'] = 'host inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Você deve informar pelo menos um destinatário.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro de SMTP: Os seguintes destinatários falharam: '; +$PHPMAILER_LANG['signing'] = 'Erro de Assinatura: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_code'] = 'Código do servidor SMTP: '; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais do servidor SMTP: '; +$PHPMAILER_LANG['smtp_detail'] = 'Detalhes do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php new file mode 100644 index 000000000..45bef9155 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php @@ -0,0 +1,33 @@ + + * @author Foster Snowhill + */ + +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; +$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; +$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; +$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; +$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; +$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; +$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php new file mode 100644 index 000000000..dce502aa0 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php @@ -0,0 +1,34 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP දෝෂය: සත්‍යාපනය අසාර්ථක විය.'; +$PHPMAILER_LANG['buggy_php'] = 'ඔබගේ PHP version එකෙහි පවතින දෝෂයක් නිසා email පණිවිඩ දෝෂ සහගත වීමේ හැකියාවක් ඇත. මෙය විසදීම සදහා SMTP භාවිතා කිරීම, mail.add_x_header INI setting එක අක්‍රීය කිරීම, MacOS හෝ Linux වලට මාරු වීම, හෝ ඔබගේ PHP version එක 7.0.17+ හෝ 7.1.3+ වලට අලුත් කිරීම කරගන්න.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP දෝෂය: සම්බන්ධ වීමට නොහැකි විය.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP දෝෂය: දත්ත පිළිගනු නොලැබේ.'; +$PHPMAILER_LANG['empty_message'] = 'පණිවිඩ අන්තර්ගතය හිස්'; +$PHPMAILER_LANG['encoding'] = 'නොදන්නා කේතනය: '; +$PHPMAILER_LANG['execute'] = 'ක්‍රියාත්මක කළ නොහැකි විය: '; +$PHPMAILER_LANG['extension_missing'] = 'Extension එක නොමැත: '; +$PHPMAILER_LANG['file_access'] = 'File එකට ප්‍රවේශ විය නොහැකි විය: '; +$PHPMAILER_LANG['file_open'] = 'File දෝෂය: File එක විවෘත කළ නොහැක: '; +$PHPMAILER_LANG['from_failed'] = 'පහත From ලිපිනයන් අසාර්ථක විය: '; +$PHPMAILER_LANG['instantiate'] = 'mail function එක ක්‍රියාත්මක කළ නොහැක.'; +$PHPMAILER_LANG['invalid_address'] = 'වලංගු නොවන ලිපිනය: '; +$PHPMAILER_LANG['invalid_header'] = 'වලංගු නොවන header නාමයක් හෝ අගයක්'; +$PHPMAILER_LANG['invalid_hostentry'] = 'වලංගු නොවන hostentry එකක්: '; +$PHPMAILER_LANG['invalid_host'] = 'වලංගු නොවන host එකක්: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer සහාය නොදක්වයි.'; +$PHPMAILER_LANG['provide_address'] = 'ඔබ අවම වශයෙන් එක් ලබන්නෙකුගේ ඊමේල් ලිපිනයක් සැපයිය යුතුය.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP දෝෂය: පහත ලබන්නන් අසමත් විය: '; +$PHPMAILER_LANG['signing'] = 'Sign කිරීමේ දෝෂය: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP කේතය: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'අමතර SMTP තොරතුරු: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP සම්බන්ධය අසාර්ථක විය.'; +$PHPMAILER_LANG['smtp_detail'] = 'තොරතුරු: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP දෝෂය: '; +$PHPMAILER_LANG['variable_set'] = 'Variable එක සැකසීමට හෝ නැවත සැකසීමට නොහැක: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php new file mode 100644 index 000000000..028f5bc49 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php @@ -0,0 +1,30 @@ + + * @author Peter Orlický + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Chyba autentifikácie.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Nebolo možné nadviazať spojenie so SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dáta neboli prijaté'; +$PHPMAILER_LANG['empty_message'] = 'Prázdne telo správy.'; +$PHPMAILER_LANG['encoding'] = 'Neznáme kódovanie: '; +$PHPMAILER_LANG['execute'] = 'Nedá sa vykonať: '; +$PHPMAILER_LANG['file_access'] = 'Súbor nebol nájdený: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Súbor sa otvoriť pre čítanie: '; +$PHPMAILER_LANG['from_failed'] = 'Následujúca adresa From je nesprávna: '; +$PHPMAILER_LANG['instantiate'] = 'Nedá sa vytvoriť inštancia emailovej funkcie.'; +$PHPMAILER_LANG['invalid_address'] = 'Neodoslané, emailová adresa je nesprávna: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Záznam hostiteľa je nesprávny: '; +$PHPMAILER_LANG['invalid_host'] = 'Hostiteľ je nesprávny: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' emailový klient nieje podporovaný.'; +$PHPMAILER_LANG['provide_address'] = 'Musíte zadať aspoň jednu emailovú adresu príjemcu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Adresy príjemcov niesu správne '; +$PHPMAILER_LANG['signing'] = 'Chyba prihlasovania: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() zlyhalo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP chyba serveru: '; +$PHPMAILER_LANG['variable_set'] = 'Nemožno nastaviť alebo resetovať premennú: '; +$PHPMAILER_LANG['extension_missing'] = 'Chýba rozšírenie: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php new file mode 100644 index 000000000..3e00c2596 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php @@ -0,0 +1,36 @@ + + * @author Filip Š + * @author Blaž Oražem + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP napaka: Avtentikacija ni uspela.'; +$PHPMAILER_LANG['buggy_php'] = 'Na vašo PHP različico vpliva napaka, ki lahko povzroči poškodovana sporočila. Če želite težavo odpraviti, preklopite na pošiljanje prek SMTP, onemogočite možnost mail.add_x_header v vaši php.ini datoteki, preklopite na MacOS ali Linux, ali nadgradite vašo PHP zaličico na 7.0.17+ ali 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP napaka: Vzpostavljanje povezave s SMTP gostiteljem ni uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP napaka: Strežnik zavrača podatke.'; +$PHPMAILER_LANG['empty_message'] = 'E-poštno sporočilo nima vsebine.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznan tip kodiranja: '; +$PHPMAILER_LANG['execute'] = 'Operacija ni uspela: '; +$PHPMAILER_LANG['extension_missing'] = 'Manjkajoča razširitev: '; +$PHPMAILER_LANG['file_access'] = 'Nimam dostopa do datoteke: '; +$PHPMAILER_LANG['file_open'] = 'Ne morem odpreti datoteke: '; +$PHPMAILER_LANG['from_failed'] = 'Neveljaven e-naslov pošiljatelja: '; +$PHPMAILER_LANG['instantiate'] = 'Ne morem inicializirati mail funkcije.'; +$PHPMAILER_LANG['invalid_address'] = 'E-poštno sporočilo ni bilo poslano. E-naslov je neveljaven: '; +$PHPMAILER_LANG['invalid_header'] = 'Neveljavno ime ali vrednost glave'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Neveljaven vnos gostitelja: '; +$PHPMAILER_LANG['invalid_host'] = 'Neveljaven gostitelj: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer ni podprt.'; +$PHPMAILER_LANG['provide_address'] = 'Prosimo, vnesite vsaj enega naslovnika.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP napaka: Sledeči naslovniki so neveljavni: '; +$PHPMAILER_LANG['signing'] = 'Napaka pri podpisovanju: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP koda: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatne informacije o SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ne morem vzpostaviti povezave s SMTP strežnikom.'; +$PHPMAILER_LANG['smtp_detail'] = 'Podrobnosti: '; +$PHPMAILER_LANG['smtp_error'] = 'Napaka SMTP strežnika: '; +$PHPMAILER_LANG['variable_set'] = 'Ne morem nastaviti oz. ponastaviti spremenljivke: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php new file mode 100644 index 000000000..0b5280f75 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: аутентификација није успела.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: подаци нису прихваћени.'; +$PHPMAILER_LANG['empty_message'] = 'Садржај поруке је празан.'; +$PHPMAILER_LANG['encoding'] = 'Непознато кодирање: '; +$PHPMAILER_LANG['execute'] = 'Није могуће извршити наредбу: '; +$PHPMAILER_LANG['file_access'] = 'Није могуће приступити датотеци: '; +$PHPMAILER_LANG['file_open'] = 'Није могуће отворити датотеку: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP грешка: слање са следећих адреса није успело: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: слање на следеће адресе није успело: '; +$PHPMAILER_LANG['instantiate'] = 'Није могуће покренути mail функцију.'; +$PHPMAILER_LANG['invalid_address'] = 'Порука није послата. Неисправна адреса: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' мејлер није подржан.'; +$PHPMAILER_LANG['provide_address'] = 'Дефинишите бар једну адресу примаоца.'; +$PHPMAILER_LANG['signing'] = 'Грешка приликом пријаве: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Није могуће задати нити ресетовати променљиву: '; +$PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php new file mode 100644 index 000000000..62138329a --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: '; +$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php new file mode 100644 index 000000000..9872c1921 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunde inte köra: '; +$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: '; +$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: '; +$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.'; +$PHPMAILER_LANG['invalid_address'] = 'Felaktig adress: '; +$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() misslyckades.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serverfel: '; +$PHPMAILER_LANG['variable_set'] = 'Kunde inte definiera eller återställa variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Tillägg ej tillgängligt: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php new file mode 100644 index 000000000..d15bed1c8 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Hindi mapatotohanan.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Hindi makakonekta sa SMTP host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Ang datos ay hindi naitanggap.'; +$PHPMAILER_LANG['empty_message'] = 'Walang laman ang mensahe'; +$PHPMAILER_LANG['encoding'] = 'Hindi alam ang encoding: '; +$PHPMAILER_LANG['execute'] = 'Hindi maisasagawa: '; +$PHPMAILER_LANG['file_access'] = 'Hindi ma-access ang file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Hindi mabuksan ang file: '; +$PHPMAILER_LANG['from_failed'] = 'Ang sumusunod na address ay nabigo: '; +$PHPMAILER_LANG['instantiate'] = 'Hindi maisimulan ang instance ng mail function.'; +$PHPMAILER_LANG['invalid_address'] = 'Hindi wasto ang address na naibigay: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'Ang mailer ay hindi suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Kailangan mong magbigay ng kahit isang email address na tatanggap.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Ang mga sumusunod na tatanggap ay nabigo: '; +$PHPMAILER_LANG['signing'] = 'Hindi ma-sign: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ang SMTP connect() ay nabigo.'; +$PHPMAILER_LANG['smtp_error'] = 'Ang server ng SMTP ay nabigo: '; +$PHPMAILER_LANG['variable_set'] = 'Hindi matatakda o ma-reset ang mga variables: '; +$PHPMAILER_LANG['extension_missing'] = 'Nawawala ang extension: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php new file mode 100644 index 000000000..f938f8020 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -0,0 +1,31 @@ + + * @fixed by Boris Yurchenko + */ + +$PHPMAILER_LANG['authenticate'] = 'Помилка SMTP: помилка авторизації.'; +$PHPMAILER_LANG['connect_host'] = 'Помилка SMTP: не вдається під\'єднатися до SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Помилка SMTP: дані не прийнято.'; +$PHPMAILER_LANG['encoding'] = 'Невідоме кодування: '; +$PHPMAILER_LANG['execute'] = 'Неможливо виконати команду: '; +$PHPMAILER_LANG['file_access'] = 'Немає доступу до файлу: '; +$PHPMAILER_LANG['file_open'] = 'Помилка файлової системи: не вдається відкрити файл: '; +$PHPMAILER_LANG['from_failed'] = 'Невірна адреса відправника: '; +$PHPMAILER_LANG['instantiate'] = 'Неможливо запустити функцію mail().'; +$PHPMAILER_LANG['provide_address'] = 'Будь ласка, введіть хоча б одну email-адресу отримувача.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - поштовий сервер не підтримується.'; +$PHPMAILER_LANG['recipients_failed'] = 'Помилка SMTP: не вдалося відправлення для таких отримувачів: '; +$PHPMAILER_LANG['empty_message'] = 'Пусте повідомлення'; +$PHPMAILER_LANG['invalid_address'] = 'Не відправлено через неправильний формат email-адреси: '; +$PHPMAILER_LANG['signing'] = 'Помилка підпису: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Помилка з\'єднання з SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Помилка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Неможливо встановити або скинути змінну: '; +$PHPMAILER_LANG['extension_missing'] = 'Розширення відсутнє: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php new file mode 100644 index 000000000..d65576e2d --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Lỗi SMTP: Không thể xác thực.'; +$PHPMAILER_LANG['connect_host'] = 'Lỗi SMTP: Không thể kết nối máy chủ SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Lỗi SMTP: Dữ liệu không được chấp nhận.'; +$PHPMAILER_LANG['empty_message'] = 'Không có nội dung'; +$PHPMAILER_LANG['encoding'] = 'Mã hóa không xác định: '; +$PHPMAILER_LANG['execute'] = 'Không thực hiện được: '; +$PHPMAILER_LANG['file_access'] = 'Không thể truy cập tệp tin '; +$PHPMAILER_LANG['file_open'] = 'Lỗi Tập tin: Không thể mở tệp tin: '; +$PHPMAILER_LANG['from_failed'] = 'Lỗi địa chỉ gửi đi: '; +$PHPMAILER_LANG['instantiate'] = 'Không dùng được các hàm gửi thư.'; +$PHPMAILER_LANG['invalid_address'] = 'Đại chỉ emai không đúng: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' trình gửi thư không được hỗ trợ.'; +$PHPMAILER_LANG['provide_address'] = 'Bạn phải cung cấp ít nhất một địa chỉ người nhận.'; +$PHPMAILER_LANG['recipients_failed'] = 'Lỗi SMTP: lỗi địa chỉ người nhận: '; +$PHPMAILER_LANG['signing'] = 'Lỗi đăng nhập: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Lỗi kết nối với SMTP'; +$PHPMAILER_LANG['smtp_error'] = 'Lỗi máy chủ smtp '; +$PHPMAILER_LANG['variable_set'] = 'Không thể thiết lập hoặc thiết lập lại biến: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php new file mode 100644 index 000000000..35e4e7000 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php @@ -0,0 +1,29 @@ + + * @author Peter Dave Hello <@PeterDaveHello/> + * @author Jason Chiang + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登入失敗。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連線到 SMTP 主機。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:無法接受的資料。'; +$PHPMAILER_LANG['empty_message'] = '郵件內容為空'; +$PHPMAILER_LANG['encoding'] = '未知編碼: '; +$PHPMAILER_LANG['execute'] = '無法執行:'; +$PHPMAILER_LANG['file_access'] = '無法存取檔案:'; +$PHPMAILER_LANG['file_open'] = '檔案錯誤:無法開啟檔案:'; +$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:'; +$PHPMAILER_LANG['instantiate'] = '未知函數呼叫。'; +$PHPMAILER_LANG['invalid_address'] = '因為電子郵件地址無效,無法傳送: '; +$PHPMAILER_LANG['mailer_not_supported'] = '不支援的發信客戶端。'; +$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:以下收件人地址錯誤:'; +$PHPMAILER_LANG['signing'] = '電子簽章錯誤: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 連線失敗'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 伺服器錯誤: '; +$PHPMAILER_LANG['variable_set'] = '無法設定或重設變數: '; +$PHPMAILER_LANG['extension_missing'] = '遺失模組 Extension: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php new file mode 100644 index 000000000..03d491165 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php @@ -0,0 +1,36 @@ + + * @author young + * @author Teddysun + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; +$PHPMAILER_LANG['buggy_php'] = '您的 PHP 版本存在漏洞,可能会导致消息损坏。为修复此问题,请切换到使用 SMTP 发送,在您的 php.ini 中禁用 mail.add_x_header 选项。切换到 MacOS 或 Linux,或将您的 PHP 升级到 7.0.17+ 或 7.1.3+ 版本。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; +$PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; +$PHPMAILER_LANG['encoding'] = '未知编码:'; +$PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['extension_missing'] = '缺少扩展名:'; +$PHPMAILER_LANG['file_access'] = '无法访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; +$PHPMAILER_LANG['from_failed'] = '发送地址错误:'; +$PHPMAILER_LANG['instantiate'] = '未知函数调用。'; +$PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是无效的:'; +$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; +$PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:'; +$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; +$PHPMAILER_LANG['invalid_header'] = '无效的标题名称或值'; +$PHPMAILER_LANG['invalid_hostentry'] = '无效的hostentry: '; +$PHPMAILER_LANG['invalid_host'] = '无效的主机:'; +$PHPMAILER_LANG['signing'] = '签名错误:'; +$PHPMAILER_LANG['smtp_code'] = 'SMTP代码: '; +$PHPMAILER_LANG['smtp_code_ex'] = '附加SMTP信息: '; +$PHPMAILER_LANG['smtp_detail'] = '详情:'; diff --git a/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php new file mode 100644 index 000000000..566c9618f --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php @@ -0,0 +1,245 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2023 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Configure PHPMailer with DSN string. + * + * @see https://en.wikipedia.org/wiki/Data_source_name + * + * @author Oleg Voronkovich + */ +class DSNConfigurator +{ + /** + * Create new PHPMailer instance configured by DSN. + * + * @param string $dsn DSN + * @param bool $exceptions Should we throw external exceptions? + * + * @return PHPMailer + */ + public static function mailer($dsn, $exceptions = null) + { + static $configurator = null; + + if (null === $configurator) { + $configurator = new DSNConfigurator(); + } + + return $configurator->configure(new PHPMailer($exceptions), $dsn); + } + + /** + * Configure PHPMailer instance with DSN string. + * + * @param PHPMailer $mailer PHPMailer instance + * @param string $dsn DSN + * + * @return PHPMailer + */ + public function configure(PHPMailer $mailer, $dsn) + { + $config = $this->parseDSN($dsn); + + $this->applyConfig($mailer, $config); + + return $mailer; + } + + /** + * Parse DSN string. + * + * @param string $dsn DSN + * + * @throws Exception If DSN is malformed + * + * @return array Configuration + */ + private function parseDSN($dsn) + { + $config = $this->parseUrl($dsn); + + if (false === $config || !isset($config['scheme']) || !isset($config['host'])) { + throw new Exception('Malformed DSN'); + } + + if (isset($config['query'])) { + parse_str($config['query'], $config['query']); + } + + return $config; + } + + /** + * Apply configuration to mailer. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + * + * @throws Exception If scheme is invalid + */ + private function applyConfig(PHPMailer $mailer, $config) + { + switch ($config['scheme']) { + case 'mail': + $mailer->isMail(); + break; + case 'sendmail': + $mailer->isSendmail(); + break; + case 'qmail': + $mailer->isQmail(); + break; + case 'smtp': + case 'smtps': + $mailer->isSMTP(); + $this->configureSMTP($mailer, $config); + break; + default: + throw new Exception( + sprintf( + 'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".', + $config['scheme'] + ) + ); + } + + if (isset($config['query'])) { + $this->configureOptions($mailer, $config['query']); + } + } + + /** + * Configure SMTP. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + */ + private function configureSMTP($mailer, $config) + { + $isSMTPS = 'smtps' === $config['scheme']; + + if ($isSMTPS) { + $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } + + $mailer->Host = $config['host']; + + if (isset($config['port'])) { + $mailer->Port = $config['port']; + } elseif ($isSMTPS) { + $mailer->Port = SMTP::DEFAULT_SECURE_PORT; + } + + $mailer->SMTPAuth = isset($config['user']) || isset($config['pass']); + + if (isset($config['user'])) { + $mailer->Username = $config['user']; + } + + if (isset($config['pass'])) { + $mailer->Password = $config['pass']; + } + } + + /** + * Configure options. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $options Options + * + * @throws Exception If option is unknown + */ + private function configureOptions(PHPMailer $mailer, $options) + { + $allowedOptions = get_object_vars($mailer); + + unset($allowedOptions['Mailer']); + unset($allowedOptions['SMTPAuth']); + unset($allowedOptions['Username']); + unset($allowedOptions['Password']); + unset($allowedOptions['Hostname']); + unset($allowedOptions['Port']); + unset($allowedOptions['ErrorInfo']); + + $allowedOptions = \array_keys($allowedOptions); + + foreach ($options as $key => $value) { + if (!in_array($key, $allowedOptions)) { + throw new Exception( + sprintf( + 'Unknown option: "%s". Allowed values: "%s"', + $key, + implode('", "', $allowedOptions) + ) + ); + } + + switch ($key) { + case 'AllowEmpty': + case 'SMTPAutoTLS': + case 'SMTPKeepAlive': + case 'SingleTo': + case 'UseSendmailOptions': + case 'do_verp': + case 'DKIM_copyHeaderFields': + $mailer->$key = (bool) $value; + break; + case 'Priority': + case 'SMTPDebug': + case 'WordWrap': + $mailer->$key = (int) $value; + break; + default: + $mailer->$key = $value; + break; + } + } + } + + /** + * Parse a URL. + * Wrapper for the built-in parse_url function to work around a bug in PHP 5.5. + * + * @param string $url URL + * + * @return array|false + */ + protected function parseUrl($url) + { + if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) { + return parse_url($url); + } + + $chunks = explode('?', $url); + if (is_array($chunks)) { + $result = parse_url($chunks[0]); + if (is_array($result)) { + $result['query'] = $chunks[1]; + } + return $result; + } + + return false; + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/Exception.php b/src/vendor/phpmailer/phpmailer/src/Exception.php new file mode 100644 index 000000000..52eaf9515 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/OAuth.php b/src/vendor/phpmailer/phpmailer/src/OAuth.php new file mode 100644 index 000000000..c1d5b7762 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/OAuth.php @@ -0,0 +1,139 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +use League\OAuth2\Client\Grant\RefreshToken; +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Token\AccessToken; + +/** + * OAuth - OAuth2 authentication wrapper class. + * Uses the oauth2-client package from the League of Extraordinary Packages. + * + * @see http://oauth2-client.thephpleague.com + * + * @author Marcus Bointon (Synchro/coolbru) + */ +class OAuth implements OAuthTokenProvider +{ + /** + * An instance of the League OAuth Client Provider. + * + * @var AbstractProvider + */ + protected $provider; + + /** + * The current OAuth access token. + * + * @var AccessToken + */ + protected $oauthToken; + + /** + * The user's email address, usually used as the login ID + * and also the from address when sending email. + * + * @var string + */ + protected $oauthUserEmail = ''; + + /** + * The client secret, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientSecret = ''; + + /** + * The client ID, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientId = ''; + + /** + * The refresh token, used to obtain new AccessTokens. + * + * @var string + */ + protected $oauthRefreshToken = ''; + + /** + * OAuth constructor. + * + * @param array $options Associative array containing + * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements + */ + public function __construct($options) + { + $this->provider = $options['provider']; + $this->oauthUserEmail = $options['userName']; + $this->oauthClientSecret = $options['clientSecret']; + $this->oauthClientId = $options['clientId']; + $this->oauthRefreshToken = $options['refreshToken']; + } + + /** + * Get a new RefreshToken. + * + * @return RefreshToken + */ + protected function getGrant() + { + return new RefreshToken(); + } + + /** + * Get a new AccessToken. + * + * @return AccessToken + */ + protected function getToken() + { + return $this->provider->getAccessToken( + $this->getGrant(), + ['refresh_token' => $this->oauthRefreshToken] + ); + } + + /** + * Generate a base64-encoded OAuth token. + * + * @return string + */ + public function getOauth64() + { + //Get a new token if it's not available or has expired + if (null === $this->oauthToken || $this->oauthToken->hasExpired()) { + $this->oauthToken = $this->getToken(); + } + + return base64_encode( + 'user=' . + $this->oauthUserEmail . + "\001auth=Bearer " . + $this->oauthToken . + "\001\001" + ); + } +} diff --git a/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php new file mode 100644 index 000000000..115550743 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php @@ -0,0 +1,44 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * OAuthTokenProvider - OAuth2 token provider interface. + * Provides base64 encoded OAuth2 auth strings for SMTP authentication. + * + * @see OAuth + * @see SMTP::authenticate() + * + * @author Peter Scopes (pdscopes) + * @author Marcus Bointon (Synchro/coolbru) + */ +interface OAuthTokenProvider +{ + /** + * Generate a base64-encoded OAuth token ensuring that the access token has not expired. + * The string to be base 64 encoded should be in the form: + * "user=\001auth=Bearer \001\001" + * + * @return string + */ + public function getOauth64(); +} diff --git a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php new file mode 100644 index 000000000..ba4bcd472 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -0,0 +1,5252 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * SMTP SMTPXClient command attibutes + * + * @var array + */ + protected $SMTPXClient = []; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.9.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; + continue; + } + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we are not connecting to localhost + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P + + diff --git a/rootfs/app/fancyindex/header.html b/rootfs/app/fancyindex/header.html new file mode 100644 index 000000000..c8e37cc41 --- /dev/null +++ b/rootfs/app/fancyindex/header.html @@ -0,0 +1,160 @@ + + + + Directory index + + + + + + + + + + + +
+

Directory: diff --git a/rootfs/app/nftd/addNginxFancyIndexForm.js b/rootfs/app/nftd/addNginxFancyIndexForm.js deleted file mode 100644 index c0d7506a2..000000000 --- a/rootfs/app/nftd/addNginxFancyIndexForm.js +++ /dev/null @@ -1,33 +0,0 @@ -// addNginxFancyIndexForm.js -// Add a small form to filter through the output of Nginx FancyIndex page -// © 2017, Lilian Besson (Naereen) and contributors, -// open-sourced under the MIT License, https://lbesson.mit-license.org/ -// hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme -var form = document.createElement('form'); -var input = document.createElement('input'); - -input.name = 'filter'; -input.id = 'search'; -input.placeholder = 'Type to search...'; - -form.appendChild(input); - -document.querySelector('h1').after(form); - -var listItems = [].slice.call(document.querySelectorAll('#list tbody tr')); - -input.addEventListener('keyup', function () { - var i, - // Word sequence _matching_ to input. All, except last, words must be _complete_. - e = "(^|.*[^\\pL])" + this.value.trim().split(/\s+/).join("([^\\pL]|[^\\pL].*[^\\pL])") + ".*$", - n = RegExp(e, "i"); - listItems.forEach(function(item) { - item.removeAttribute('hidden'); - }); - listItems.filter(function(item) { - i = item.querySelector('td').textContent.replace(/\s+/g, " "); - return !n.test(i); - }).forEach(function(item) { - item.hidden = true; - }); -}); diff --git a/rootfs/app/nftd/footer.html b/rootfs/app/nftd/footer.html deleted file mode 100644 index 09aafd532..000000000 --- a/rootfs/app/nftd/footer.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/rootfs/app/nftd/header.html b/rootfs/app/nftd/header.html deleted file mode 100644 index b60435668..000000000 --- a/rootfs/app/nftd/header.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - Nginx Directory - - - - - - -

Directory: diff --git a/rootfs/app/nftd/jquery.min.js b/rootfs/app/nftd/jquery.min.js deleted file mode 100644 index b98d8c09f..000000000 --- a/rootfs/app/nftd/jquery.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v4.0.0-beta | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=t(e,!0):t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";if(!e.document)throw Error("jQuery requires a window with a document");var n=[],r=Object.getPrototypeOf,i=n.slice,o=n.flat?function(e){return n.flat.call(e)}:function(e){return n.concat.apply([],e)},a=n.push,s=n.indexOf,u={},l=u.toString,c=u.hasOwnProperty,f=c.toString,p=f.call(Object),d={};function h(e){return null==e?e+"":"object"==typeof e?u[l.call(e)]||"object":typeof e}function g(e){return null!=e&&e===e.window}function y(e){var t=!!e&&e.length,n=h(e);return!("function"==typeof e||g(e))&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}var v=e.document,m={type:!0,src:!0,nonce:!0,noModule:!0};function x(e,t,n){var r,i=(n=n||v).createElement("script");for(r in i.text=e,m)t&&t[r]&&(i[r]=t[r]);n.head.appendChild(i).parentNode&&i.parentNode.removeChild(i)}var b="4.0.0-beta",w=/HTML$/i,T=function(e,t){return new T.fn.init(e,t)};function C(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}T.fn=T.prototype={jquery:b,constructor:T,length:0,toArray:function(){return i.call(this)},get:function(e){return null==e?i.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=T.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return T.each(this,e)},map:function(e){return this.pushStack(T.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(i.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(T.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(T.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+E+")"+E+"*"),N=RegExp(E+"|>"),O=/[+~]/,H=v.documentElement,L=H.matches||H.msMatchesSelector;function P(){var e=[];function t(n,r){return e.push(n+" ")>T.expr.cacheLength&&delete t[e.shift()],t[n+" "]=r}return t}function R(e){return e&&void 0!==e.getElementsByTagName&&e}var M="\\["+E+"*("+A+")(?:"+E+"*([*^$|!~]?=)"+E+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+A+"))|)"+E+"*\\]",W=":("+A+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",I={ID:RegExp("^#("+A+")"),CLASS:RegExp("^\\.("+A+")"),TAG:RegExp("^("+A+"|[*])"),ATTR:RegExp("^"+M),PSEUDO:RegExp("^"+W),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i")},$=new RegExp(W),F=RegExp("\\\\[\\da-fA-F]{1,6}"+E+"?|\\\\([^\\r\\n\\f])","g"),B=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))};function _(e){return e.replace(F,B)}function U(e){T.error("Syntax error, unrecognized expression: "+e)}var X=RegExp("^"+E+"*,"+E+"*"),z=P();function V(e,t){var n,r,i,o,a,s,u,l=z[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=T.expr.preFilter;while(a){for(o in(!n||(r=X.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=q.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(D," ")}),a=a.slice(n.length)),I)(r=T.expr.match[o].exec(a))&&(!u[o]||(r=u[o](r)))&&(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?U(e):z(e,s).slice(0)}function Y(e){for(var t=0,n=e.length,r="";t1)},removeAttr:function(e){return this.each(function(){T.removeAttr(this,e)})}}),T.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o){if(void 0===e.getAttribute)return T.prop(e,t,n);if(1===o&&T.isXMLDoc(e)||(i=T.attrHooks[t.toLowerCase()]),void 0!==n){if(null===n){T.removeAttr(e,t);return}return i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n),n)}return i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=e.getAttribute(t))?void 0:r}},attrHooks:{},removeAttr:function(e,t){var n,r=0,i=t&&t.match(Q);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),k&&(T.attrHooks.type={set:function(e,t){if("radio"===t&&C(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}),T.each("checked selected async autofocus autoplay controls defer disabled hidden ismap loop multiple open readonly required scoped".split(" "),function(e,t){T.attrHooks[t]={get:function(e){return null!=e.getAttribute(t)?t.toLowerCase():null},set:function(e,t,n){return!1===t?T.removeAttr(e,n):e.setAttribute(n,n),n}}});var J=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function K(e,t){return t?"\x00"===e?"\uFFFD":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}T.escapeSelector=function(e){return(e+"").replace(J,K)};var Z=n.sort,ee=n.splice;function et(e,t){if(e===t)return en=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)?e==v||e.ownerDocument==v&&T.contains(v,e)?-1:t==v||t.ownerDocument==v&&T.contains(v,t)?1:0:4&n?-1:1)}T.uniqueSort=function(e){var t,n=[],r=0,i=0;if(en=!1,Z.call(e,et),en){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)ee.call(e,n[r],1)}return e},T.fn.uniqueSort=function(){return this.pushStack(T.uniqueSort(i.apply(this)))};var en,er,ei,eo,ea,es,eu=0,el=0,ec=P(),ef=P(),ep=P(),ed=RegExp(E+"+","g"),eh=RegExp("^"+A+"$"),eg=T.extend({needsContext:RegExp("^"+E+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)","i")},I),ey=/^(?:input|select|textarea|button)$/i,ev=/^h\d$/i,em=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ex=function(){eE()},eb=eS(function(e){return!0===e.disabled&&C(e,"fieldset")},{dir:"parentNode",next:"legend"});function ew(e,t,n,r){var i,o,s,u,l,c,f,p=t&&t.ownerDocument,d=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==d&&9!==d&&11!==d)return n;if(!r&&(eE(t),t=t||eo,es)){if(11!==d&&(l=em.exec(e))){if(i=l[1]){if(9===d)return(s=t.getElementById(i))&&a.call(n,s),n;if(p&&(s=p.getElementById(i))&&T.contains(t,s))return a.call(n,s),n}else if(l[2])return a.apply(n,t.getElementsByTagName(e)),n;else if((i=l[3])&&t.getElementsByClassName)return a.apply(n,t.getElementsByClassName(i)),n}if(!ep[e+" "]&&(!S||!S.test(e))){if(f=e,p=t,1===d&&(N.test(e)||q.test(e))){((p=O.test(e)&&R(t.parentNode)||t)!=t||k)&&((u=t.getAttribute("id"))?u=T.escapeSelector(u):t.setAttribute("id",u=T.expando)),o=(c=V(e)).length;while(o--)c[o]=(u?"#"+u:":scope")+" "+Y(c[o]);f=c.join(",")}try{return a.apply(n,p.querySelectorAll(f)),n}catch(t){ep(e,!0)}finally{u===T.expando&&t.removeAttribute("id")}}}return eN(e.replace(D,"$1"),t,n,r)}function eT(e){return e[T.expando]=!0,e}function eC(e){return function(t){if("form"in t)return t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||!e!==t.isDisabled&&eb(t)===e:t.disabled===e;return"label"in t&&t.disabled===e}}function ej(e){return eT(function(t){return t=+t,eT(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function eE(e){var t,n=e?e.ownerDocument||e:v;n!=eo&&9===n.nodeType&&(ea=(eo=n).documentElement,es=!T.isXMLDoc(eo),k&&v!=eo&&(t=eo.defaultView)&&t.top!==t&&t.addEventListener("unload",ex))}for(er in ew.matches=function(e,t){return ew(e,null,null,t)},ew.matchesSelector=function(e,t){if(eE(e),es&&!ep[t+" "]&&(!S||!S.test(t)))try{return L.call(e,t)}catch(e){ep(t,!0)}return ew(t,eo,null,[e]).length>0},T.expr={cacheLength:50,createPseudo:eT,match:eg,find:{ID:function(e,t){if(void 0!==t.getElementById&&es){var n=t.getElementById(e);return n?[n]:[]}},TAG:function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},CLASS:function(e,t){if(void 0!==t.getElementsByClassName&&es)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=_(e[1]),e[3]=_(e[3]||e[4]||e[5]||""),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||U(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&U(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return I.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&$.test(n)&&(t=V(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{ID:function(e){var t=_(e);return function(e){return e.getAttribute("id")===t}},TAG:function(e){var t=_(e).toLowerCase();return"*"===e?function(){return!0}:function(e){return C(e,t)}},CLASS:function(e){var t=ec[e+" "];return t||(t=RegExp("(^|"+E+")"+e+"("+E+"|$)"),ec(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")}))},ATTR:function(e,t,n){return function(r){var i=T.attr(r,e);return null==i?"!="===t:!t||((i+="","="===t)?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(ed," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h=o!==a?"nextSibling":"previousSibling",g=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,m=!1;if(g){if(o){while(h){f=t;while(f=f[h])if(s?C(f,y):1===f.nodeType)return!1;d=h="only"===e&&!d&&"nextSibling"}return!0}if(d=[a?g.firstChild:g.lastChild],a&&v){m=(p=(l=(c=g[T.expando]||(g[T.expando]={}))[e]||[])[0]===eu&&l[1])&&l[2],f=p&&g.childNodes[p];while(f=++p&&f&&f[h]||(m=p=0)||d.pop())if(1===f.nodeType&&++m&&f===t){c[e]=[eu,p,m];break}}else if(v&&(m=p=(l=(c=t[T.expando]||(t[T.expando]={}))[e]||[])[0]===eu&&l[1]),!1===m){while(f=++p&&f&&f[h]||(m=p=0)||d.pop())if((s?C(f,y):1===f.nodeType)&&++m&&(v&&((c=f[T.expando]||(f[T.expando]={}))[e]=[eu,m]),f===t))break}return(m-=i)===r||m%r==0&&m/r>=0}}},PSEUDO:function(e,t){var n=T.expr.pseudos[e]||T.expr.setFilters[e.toLowerCase()]||U("unsupported pseudo: "+e);return n[T.expando]?n(t):n}},pseudos:{not:eT(function(e){var t=[],n=[],r=eq(e.replace(D,"$1"));return r[T.expando]?eT(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:eT(function(e){return function(t){return ew(e,t).length>0}}),contains:eT(function(e){return e=_(e),function(t){return(t.textContent||T.text(t)).indexOf(e)>-1}}),lang:eT(function(e){return eh.test(e||"")||U("unsupported lang: "+e),e=_(e).toLowerCase(),function(t){var n;do if(n=es?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===ea},focus:function(e){return e===eo.activeElement&&eo.hasFocus()&&!!(e.type||e.href||~e.tabIndex)},enabled:eC(!1),disabled:eC(!0),checked:function(e){return C(e,"input")&&!!e.checked||C(e,"option")&&!!e.selected},selected:function(e){return k&&e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.expr.pseudos.empty(e)},header:function(e){return ev.test(e.nodeName)},input:function(e){return ey.test(e.nodeName)},button:function(e){return C(e,"input")&&"button"===e.type||C(e,"button")},text:function(e){return C(e,"input")&&"text"===e.type},first:ej(function(){return[0]}),last:ej(function(e,t){return[t-1]}),eq:ej(function(e,t,n){return[n<0?n+t:n]}),even:ej(function(e,t){for(var n=0;nt?t:n;--r>=0;)e.push(r);return e}),gt:ej(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function eA(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s-1},l,!0),d=[function(e,t,r){var i=!u&&(r||t!=ei)||((n=t).nodeType?f(e,t,r):p(e,t,r));return n=null,i}];c-1&&(e[f]=!(u[f]=d))}}else h=eA(h===u?h.splice(v,h.length):h),o?o(null,u,h,c):a.apply(u,h)})}(c>1&&eD(d),c>1&&Y(t.slice(0,c-1).concat({value:" "===t[c-2].type?"*":""})).replace(D,"$1"),r,c0,r=l.length>0,i=function(e,t,i,o,s){var c,f,p,d=0,h="0",g=e&&[],y=[],v=ei,m=e||r&&T.expr.find.TAG("*",s),x=eu+=null==v?1:Math.random()||.1;for(s&&(ei=t==eo||t||s);null!=(c=m[h]);h++){if(r&&c){f=0,t||c.ownerDocument==eo||(eE(c),i=!es);while(p=l[f++])if(p(c,t||eo,i)){a.call(o,c);break}s&&(eu=x)}n&&((c=!p&&c)&&d--,e&&g.push(c))}if(d+=h,n&&h!==d){f=0;while(p=u[f++])p(g,y,t,i);if(e){if(d>0)while(h--)g[h]||y[h]||(y[h]=j.call(o));y=eA(y)}a.apply(o,y),s&&!e&&y.length>0&&d+u.length>1&&T.uniqueSort(o)}return s&&(eu=x,ei=v),g},n?eT(i):i))).selector=e}return c}function eN(e,t,n,r){var i,o,s,u,l,c="function"==typeof e&&e,f=!r&&V(e=c.selector||e);if(n=n||[],1===f.length){if((o=f[0]=f[0].slice(0)).length>2&&"ID"===(s=o[0]).type&&9===t.nodeType&&es&&T.expr.relative[o[1].type]){if(!(t=(T.expr.find.ID(_(s.matches[0]),t)||[])[0]))return n;c&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=eg.needsContext.test(e)?0:o.length;while(i--){if(s=o[i],T.expr.relative[u=s.type])break;if((l=T.expr.find[u])&&(r=l(_(s.matches[0]),O.test(o[0].type)&&R(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&Y(o)))return a.apply(n,r),n;break}}}return(c||eq(e,f))(r,t,!es,n,!t||O.test(e)&&R(t.parentNode)||t),n}function eO(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&T(e).is(n))break;r.push(e)}return r}function eH(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}ek.prototype=T.expr.filters=T.expr.pseudos,T.expr.setFilters=new ek,eE(),T.find=ew,ew.compile=eq,ew.select=eN,ew.setDocument=eE,ew.tokenize=V;var eL=T.expr.match.needsContext,eP=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function eR(e){return"<"===e[0]&&">"===e[e.length-1]&&e.length>=3}function eM(e,t,n){return"function"==typeof t?T.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?T.grep(e,function(e){return e===t!==n}):"string"!=typeof t?T.grep(e,function(e){return s.call(t,e)>-1!==n}):T.filter(t,e,n)}T.filter=function(e,t,n){var r=t[0];return(n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType)?T.find.matchesSelector(r,e)?[r]:[]:T.find.matches(e,T.grep(t,function(e){return 1===e.nodeType}))},T.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(T(e).filter(function(){for(t=0;t1?T.uniqueSort(n):n},filter:function(e){return this.pushStack(eM(this,e||[],!1))},not:function(e){return this.pushStack(eM(this,e||[],!0))},is:function(e){return!!eM(this,"string"==typeof e&&eL.test(e)?T(e):e||[],!1).length}});var eW,eI=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(T.fn.init=function(e,t){var n,r;if(!e)return this;if(e.nodeType)return this[0]=e,this.length=1,this;if("function"==typeof e)return void 0!==eW.ready?eW.ready(e):e(T);if(eR(n=e+""))n=[null,e,null];else{if("string"!=typeof e)return T.makeArray(e,this);n=eI.exec(e)}if(n&&(n[1]||!t)){if(!n[1])return(r=v.getElementById(n[2]))&&(this[0]=r,this.length=1),this;if(t=t instanceof T?t[0]:t,T.merge(this,T.parseHTML(n[1],t&&t.nodeType?t.ownerDocument||t:v,!0)),eP.test(n[1])&&T.isPlainObject(t))for(n in t)"function"==typeof this[n]?this[n](t[n]):this.attr(n,t[n]);return this}return!t||t.jquery?(t||eW).find(e):this.constructor(t).find(e)}).prototype=T.fn,eW=T(v);var e$=/^(?:parents|prev(?:Until|All))/,eF={children:!0,contents:!0,next:!0,prev:!0};function eB(e,t){while((e=e[t])&&1!==e.nodeType);return e}function e_(e){return e}function eU(e){throw e}function eX(e,t,n,r){var i;try{e&&"function"==typeof(i=e.promise)?i.call(e).done(t).fail(n):e&&"function"==typeof(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n(e)}}T.fn.extend({has:function(e){var t=T(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&T.find.matchesSelector(n,e))){o.push(n);break}}return this.pushStack(o.length>1?T.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?s.call(T(e),this[0]):s.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(T.uniqueSort(T.merge(this.get(),T(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),T.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return eO(e,"parentNode")},parentsUntil:function(e,t,n){return eO(e,"parentNode",n)},next:function(e){return eB(e,"nextSibling")},prev:function(e){return eB(e,"previousSibling")},nextAll:function(e){return eO(e,"nextSibling")},prevAll:function(e){return eO(e,"previousSibling")},nextUntil:function(e,t,n){return eO(e,"nextSibling",n)},prevUntil:function(e,t,n){return eO(e,"previousSibling",n)},siblings:function(e){return eH((e.parentNode||{}).firstChild,e)},children:function(e){return eH(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(C(e,"template")&&(e=e.content||e),T.merge([],e.childNodes))}},function(e,t){T.fn[e]=function(n,r){var i=T.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=T.filter(r,i)),this.length>1&&(eF[e]||T.uniqueSort(i),e$.test(e)&&i.reverse()),this.pushStack(i)}}),T.Callbacks=function(e){e="string"==typeof e?(t=e,n={},T.each(t.match(Q)||[],function(e,t){n[t]=!0}),n):T.extend({},e);var t,n,r,i,o,a,s=[],u=[],l=-1,c=function(){for(a=a||e.once,o=r=!0;u.length;l=-1){i=u.shift();while(++l-1)s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?T.inArray(e,s)>-1:s.length>0},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=i="",this},disabled:function(){return!s},lock:function(){return a=u=[],i||r||(s=i=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),r||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},T.extend({Deferred:function(t){var n=[["notify","progress",T.Callbacks("memory"),T.Callbacks("memory"),2],["resolve","done",T.Callbacks("once memory"),T.Callbacks("once memory"),0,"resolved"],["reject","fail",T.Callbacks("once memory"),T.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return T.Deferred(function(t){T.each(n,function(n,r){var i="function"==typeof e[r[4]]&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&"function"==typeof e.promise?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==eU&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(T.Deferred.getErrorHook&&(c.error=T.Deferred.getErrorHook()),e.setTimeout(c))}}return T.Deferred(function(e){n[0][3].add(a(0,e,"function"==typeof i?i:e_,e.notifyWith)),n[1][3].add(a(0,e,"function"==typeof t?t:e_)),n[2][3].add(a(0,e,"function"==typeof r?r:eU))}).promise()},promise:function(e){return null!=e?T.extend(e,i):i}},o={};return T.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),o=i.call(arguments),a=T.Deferred(),s=function(e){return function(n){r[e]=this,o[e]=arguments.length>1?i.call(arguments):n,--t||a.resolveWith(r,o)}};if(t<=1&&(eX(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||"function"==typeof(o[n]&&o[n].then)))return a.then();while(n--)eX(o[n],s(n),a.reject);return a.promise()}});var ez=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;T.Deferred.exceptionHook=function(t,n){t&&ez.test(t.name)&&e.console.warn("jQuery.Deferred exception",t,n)},T.readyException=function(t){e.setTimeout(function(){throw t})};var eV=T.Deferred();function eY(){v.removeEventListener("DOMContentLoaded",eY),e.removeEventListener("load",eY),T.ready()}T.fn.ready=function(e){return eV.then(e).catch(function(e){T.readyException(e)}),this},T.extend({isReady:!1,readyWait:1,ready:function(e){!(!0===e?--T.readyWait:T.isReady)&&(T.isReady=!0,!0!==e&&--T.readyWait>0||eV.resolveWith(v,[T]))}}),T.ready.then=eV.then,"loading"!==v.readyState?e.setTimeout(T.ready):(v.addEventListener("DOMContentLoaded",eY),e.addEventListener("load",eY));var eG=/-([a-z])/g;function eQ(e,t){return t.toUpperCase()}function eJ(e){return e.replace(eG,eQ)}function eK(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType}function eZ(){this.expando=T.expando+eZ.uid++}eZ.uid=1,eZ.prototype={cache:function(e){var t=e[this.expando];return!t&&(t=Object.create(null),eK(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[eJ(t)]=n;else for(r in t)i[eJ(r)]=t[r];return n},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][eJ(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(eJ):(t=eJ(t))in r?[t]:t.match(Q)||[]).length;while(n--)delete r[t[n]]}(void 0===t||T.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!T.isEmptyObject(t)}};var e0=new eZ,e1=new eZ,e2=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,e3=/[A-Z]/g;function e4(e,t,n){var r,i;if(void 0===n&&1===e.nodeType){if(r="data-"+t.replace(e3,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{i=n,n="true"===i||"false"!==i&&("null"===i?null:i===+i+""?+i:e2.test(i)?JSON.parse(i):i)}catch(e){}e1.set(e,t,n)}else n=void 0}return n}T.extend({hasData:function(e){return e1.hasData(e)||e0.hasData(e)},data:function(e,t,n){return e1.access(e,t,n)},removeData:function(e,t){e1.remove(e,t)},_data:function(e,t,n){return e0.access(e,t,n)},_removeData:function(e,t){e0.remove(e,t)}}),T.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=e1.get(o),1===o.nodeType&&!e0.get(o,"hasDataAttrs"))){n=a.length;while(n--)a[n]&&0===(r=a[n].name).indexOf("data-")&&e4(o,r=eJ(r.slice(5)),i[r]);e0.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){e1.set(this,e)}):G(this,function(t){var n;if(o&&void 0===t)return void 0!==(n=e1.get(o,e))||void 0!==(n=e4(o,e))?n:void 0;this.each(function(){e1.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){e1.remove(this,e)})}}),T.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=e0.get(e,t),n&&(!r||Array.isArray(n)?r=e0.set(e,t,T.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=T.queue(e,t),r=n.length,i=n.shift(),o=T._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){T.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return e0.get(e,n)||e0.set(e,n,{empty:T.Callbacks("once memory").add(function(){e0.remove(e,[t+"queue",n])})})}}),T.fn.extend({queue:function(e,t){var n=2;return("string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]*)/i,tc={thead:["table"],col:["colgroup","table"],tr:["tbody","table"],td:["tr","tbody","table"]};function tf(e,t){var n;return(n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&C(e,t))?T.merge([e],n):n}tc.tbody=tc.tfoot=tc.colgroup=tc.caption=tc.thead,tc.th=tc.td;var tp=/^$|^module$|\/(?:java|ecma)script/i;function td(e,t){for(var n=0,r=e.length;n-1)s=s.appendChild(t.createElement(u[c]));s.innerHTML=T.htmlPrefilter(a),T.merge(p,s.childNodes),(s=f.firstChild).textContent=""}else p.push(t.createTextNode(a))}f.textContent="",d=0;while(a=p[d++]){if(i&&T.inArray(a,i)>-1){o&&o.push(a);continue}if(l=ts(a),s=tf(f.appendChild(a),"script"),l&&td(s),r){c=0;while(a=s[c++])tp.test(a.type||"")&&r.push(a)}}return f}function ty(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function tv(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function tm(e,t,n,r){t=o(t);var i,a,s,u,l,c,f=0,p=e.length,d=p-1,h=t[0];if("function"==typeof h)return e.each(function(i){var o=e.eq(i);t[0]=h.call(this,i,o.html()),tm(o,t,n,r)});if(p&&(a=(i=tg(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=a),a||r)){for(u=(s=T.map(tf(i,"script"),ty)).length;f=1)){for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&!("click"===e.type&&!0===l.disabled)){for(n=0,o=[],a={};n-1:T.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}}return l=this,u0&&td(a,!u&&tf(e,"script")),s},cleanData:function(e){for(var t,n,r,i=T.event.special,o=0;void 0!==(n=e[o]);o++)if(eK(n)){if(t=n[e0.expando]){if(t.events)for(r in t.events)i[r]?T.event.remove(n,r):T.removeEvent(n,r,t.handle);n[e0.expando]=void 0}n[e1.expando]&&(n[e1.expando]=void 0)}}}),T.fn.extend({detach:function(e){return tD(this,e,!0)},remove:function(e){return tD(this,e)},text:function(e){return G(this,function(e){return void 0===e?T.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=e)})},null,e,arguments.length)},append:function(){return tm(this,arguments,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&tk(this,e).appendChild(e)})},prepend:function(){return tm(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=tk(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return tm(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return tm(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(T.cleanData(tf(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return T.clone(this,e,t)})},html:function(e){return G(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!tE.test(e)&&!tc[(tl.exec(e)||["",""])[1].toLowerCase()]){e=T.htmlPrefilter(e);try{for(;nT.inArray(this,e)&&(T.cleanData(tf(this)),n&&n.replaceChild(t,this))},e)}}),T.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){T.fn[e]=function(e){for(var n,r=[],i=T(e),o=i.length-1,s=0;s<=o;s++)n=s===o?this:this.clone(!0),T(i[s])[t](n),a.apply(r,n);return this.pushStack(r)}});var tA=RegExp("^("+e5+")(?!px)[a-z%]+$","i"),tq=/^--/;function tN(t){var n=t.ownerDocument.defaultView;return n||(n=e),n.getComputedStyle(t)}function tO(e,t,n){var r,i=tq.test(t);return(n=n||tN(e))&&(r=n.getPropertyValue(t)||n[t],i&&r&&(r=r.replace(D,"$1")||void 0),""!==r||ts(e)||(r=T.style(e,t))),void 0!==r?r+"":r}var tH=["Webkit","Moz","ms"],tL=v.createElement("div").style,tP={};function tR(e){return tP[e]||(e in tL?e:tP[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=tH.length;while(n--)if((e=tH[n]+t)in tL)return e}(e)||e)}(tX=v.createElement("div")).style&&(d.reliableTrDimensions=function(){var t,n,r;if(null==tU){if(t=v.createElement("table"),n=v.createElement("tr"),t.style.cssText="position:absolute;left:-11111px;border-collapse:separate",n.style.cssText="box-sizing:content-box;border:1px solid",n.style.height="1px",tX.style.height="9px",tX.style.display="block",H.appendChild(t).appendChild(n).appendChild(tX),0===t.offsetWidth){H.removeChild(t);return}tU=parseInt((r=e.getComputedStyle(n)).height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===n.offsetHeight,H.removeChild(t)}return tU});var tM=/^(none|table(?!-c[ea]).+)/,tW={position:"absolute",visibility:"hidden",display:"block"},tI={letterSpacing:"0",fontWeight:"400"};function t$(e,t,n){var r=e9.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function tF(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0,l=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(l+=T.css(e,n+e6[a],!0,i)),r?("content"===n&&(u-=T.css(e,"padding"+e6[a],!0,i)),"margin"!==n&&(u-=T.css(e,"border"+e6[a]+"Width",!0,i))):(u+=T.css(e,"padding"+e6[a],!0,i),"padding"!==n?u+=T.css(e,"border"+e6[a]+"Width",!0,i):s+=T.css(e,"border"+e6[a]+"Width",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u+l}function tB(e,t,n){var r=tN(e),i=(k||n)&&"border-box"===T.css(e,"boxSizing",!1,r),o=i,a=tO(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(tA.test(a)){if(!n)return a;a="auto"}return("auto"===a||k&&i||!d.reliableTrDimensions()&&C(e,"tr"))&&e.getClientRects().length&&(i="border-box"===T.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+tF(e,t,n||(i?"border":"content"),o,r,a)+"px"}function t_(e,t,n,r,i){return new t_.prototype.init(e,t,n,r,i)}T.extend({cssHooks:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=ti(t),u=tq.test(t),l=e.style;if(u||(t=tR(s)),a=T.cssHooks[t]||T.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=e9.exec(n))&&i[1]&&(n=tn(e,t,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(tt(s)?"px":"")),k&&""===n&&0===t.indexOf("background")&&(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=ti(t);return(tq.test(t)||(t=tR(s)),(a=T.cssHooks[t]||T.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=tO(e,t,r)),"normal"===i&&t in tI&&(i=tI[t]),""===n||n)?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),T.each(["height","width"],function(e,t){T.cssHooks[t]={get:function(e,n,r){if(n)return!tM.test(T.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?tB(e,t,r):function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r}(e,tW,function(){return tB(e,t,r)})},set:function(e,n,r){var i,o=tN(e),a=r&&"border-box"===T.css(e,"boxSizing",!1,o),s=r?tF(e,t,r,a,o):0;return s&&(i=e9.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=T.css(e,t)),t$(e,n,s)}}}),T.each({margin:"",padding:"",border:"Width"},function(e,t){T.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+e6[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(T.cssHooks[e+t].set=t$)}),T.fn.extend({css:function(e,t){return G(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=tN(e),i=t.length;a1)}}),T.Tween=t_,t_.prototype={constructor:t_,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||T.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(tt(n)?"px":"")},cur:function(){var e=t_.propHooks[this.prop];return e&&e.get?e.get(this):t_.propHooks._default.get(this)},run:function(e){var t,n=t_.propHooks[this.prop];return this.options.duration?this.pos=t=T.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):t_.propHooks._default.set(this),this}},t_.prototype.init.prototype=t_.prototype,t_.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=T.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){T.fx.step[e.prop]?T.fx.step[e.prop](e):1===e.elem.nodeType&&(T.cssHooks[e.prop]||null!=e.elem.style[tR(e.prop)])?T.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},T.easing={linear:function(e){return e},swing:function(e){return .5-Math.cos(e*Math.PI)/2},_default:"swing"},T.fx=t_.prototype.init,T.fx.step={};var tU,tX,tz,tV,tY=/^(?:toggle|show|hide)$/,tG=/queueHooks$/;function tQ(){return e.setTimeout(function(){tz=void 0}),tz=Date.now()}function tJ(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=e6[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function tK(e,t,n){for(var r,i=(tZ.tweeners[t]||[]).concat(tZ.tweeners["*"]),o=0,a=i.length;o1)},removeProp:function(e){return this.each(function(){delete this[T.propFix[e]||e]})}}),T.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return(1===o&&T.isXMLDoc(e)||(t=T.propFix[t]||t,i=T.propHooks[t]),void 0!==n)?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=e.getAttribute("tabindex");return t?parseInt(t,10):t0.test(e.nodeName)||t1.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),k&&(T.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),T.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){T.propFix[this.toLowerCase()]=this}),T.fn.extend({addClass:function(e){var t,n,r,i,o,a;return"function"==typeof e?this.each(function(t){T(this).addClass(e.call(this,t,t3(this)))}):(t=t4(e)).length?this.each(function(){if(r=t3(this),n=1===this.nodeType&&" "+t2(r)+" "){for(o=0;on.indexOf(" "+i+" ")&&(n+=i+" ");r!==(a=t2(n))&&this.setAttribute("class",a)}}):this},removeClass:function(e){var t,n,r,i,o,a;return"function"==typeof e?this.each(function(t){T(this).removeClass(e.call(this,t,t3(this)))}):arguments.length?(t=t4(e)).length?this.each(function(){if(r=t3(this),n=1===this.nodeType&&" "+t2(r)+" "){for(o=0;o-1)n=n.replace(" "+i+" "," ")}r!==(a=t2(n))&&this.setAttribute("class",a)}}):this:this.attr("class","")},toggleClass:function(e,t){var n,r,i,o;return"function"==typeof e?this.each(function(n){T(this).toggleClass(e.call(this,n,t3(this),t),t)}):"boolean"==typeof t?t?this.addClass(e):this.removeClass(e):(n=t4(e)).length?this.each(function(){for(i=0,o=T(this);i-1)return!0;return!1}}),T.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r="function"==typeof e,this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,T(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=T.map(i,function(e){return null==e?"":e+""})),(t=T.valHooks[this.type]||T.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=T.valHooks[i.type]||T.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:null==(n=i.value)?"":n:void 0}}),T.extend({valHooks:{select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k&&(T.valHooks.option={get:function(e){var t=e.getAttribute("value");return null!=t?t:t2(T.text(e))}}),T.each(["radio","checkbox"],function(){T.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=T.inArray(T(e).val(),t)>-1}}});var t5=/^(?:focusinfocus|focusoutblur)$/,t9=function(e){e.stopPropagation()};T.extend(T.event,{trigger:function(t,n,r,i){var o,a,s,u,l,f,p,d,h=[r||v],y=c.call(t,"type")?t.type:t,m=c.call(t,"namespace")?t.namespace.split("."):[];if(a=d=s=r=r||v,!(3===r.nodeType||8===r.nodeType||t5.test(y+T.event.triggered))&&(y.indexOf(".")>-1&&(y=(m=y.split(".")).shift(),m.sort()),l=0>y.indexOf(":")&&"on"+y,(t=t[T.expando]?t:new T.Event(y,"object"==typeof t&&t)).isTrigger=i?2:3,t.namespace=m.join("."),t.rnamespace=t.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:T.makeArray(n,[t]),p=T.event.special[y]||{},i||!p.trigger||!1!==p.trigger.apply(r,n))){if(!i&&!p.noBubble&&!g(r)){for(u=p.delegateType||y,t5.test(u+y)||(a=a.parentNode);a;a=a.parentNode)h.push(a),s=a;s===(r.ownerDocument||v)&&h.push(s.defaultView||s.parentWindow||e)}o=0;while((a=h[o++])&&!t.isPropagationStopped())d=a,t.type=o>1?u:p.bindType||y,(f=(e0.get(a,"events")||Object.create(null))[t.type]&&e0.get(a,"handle"))&&f.apply(a,n),(f=l&&a[l])&&f.apply&&eK(a)&&(t.result=f.apply(a,n),!1===t.result&&t.preventDefault());return t.type=y,!i&&!t.isDefaultPrevented()&&(!p._default||!1===p._default.apply(h.pop(),n))&&eK(r)&&l&&"function"==typeof r[y]&&!g(r)&&((s=r[l])&&(r[l]=null),T.event.triggered=y,t.isPropagationStopped()&&d.addEventListener(y,t9),r[y](),t.isPropagationStopped()&&d.removeEventListener(y,t9),T.event.triggered=void 0,s&&(r[l]=s)),t.result}},simulate:function(e,t,n){var r=T.extend(new T.Event,n,{type:e,isSimulated:!0});T.event.trigger(r,null,t)}}),T.fn.extend({trigger:function(e,t){return this.each(function(){T.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return T.event.trigger(e,t,n,!0)}});var t6=e.location,t8={guid:Date.now()},t7=/\?/;T.parseXML=function(t){var n,r;if(!t||"string"!=typeof t)return null;try{n=new e.DOMParser().parseFromString(t,"text/xml")}catch(e){}return r=n&&n.getElementsByTagName("parsererror")[0],(!n||r)&&T.error("Invalid XML: "+(r?T.map(r.childNodes,function(e){return e.textContent}).join("\n"):t)),n};var ne=/\[\]$/,nt=/\r?\n/g,nn=/^(?:submit|button|image|reset|file)$/i,nr=/^(?:input|select|textarea|keygen)/i;T.param=function(e,t){var n,r=[],i=function(e,t){var n="function"==typeof t?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!T.isPlainObject(e))T.each(e,function(){i(this.name,this.value)});else for(n in e)!function e(t,n,r,i){var o;if(Array.isArray(n))T.each(n,function(n,o){r||ne.test(t)?i(t,o):e(t+"["+("object"==typeof o&&null!=o?n:"")+"]",o,r,i)});else if(r||"object"!==h(n))i(t,n);else for(o in n)e(t+"["+o+"]",n[o],r,i)}(n,e[n],t,i);return r.join("&")},T.fn.extend({serialize:function(){return T.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=T.prop(this,"elements");return e?T.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!T(this).is(":disabled")&&nr.test(this.nodeName)&&!nn.test(e)&&(this.checked||!tx.test(e))}).map(function(e,t){var n=T(this).val();return null==n?null:Array.isArray(n)?T.map(n,function(e){return{name:t.name,value:e.replace(nt,"\r\n")}}):{name:t.name,value:n.replace(nt,"\r\n")}}).get()}});var ni=/%20/g,no=/#.*$/,na=/([?&])_=[^&]*/,ns=/^(.*?):[ \t]*([^\r\n]*)$/mg,nu=/^(?:GET|HEAD)$/,nl=/^\/\//,nc={},nf={},np="*/".concat("*"),nd=v.createElement("a");function nh(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(Q)||[];if("function"==typeof n)while(r=o[i++])"+"===r[0]?(e[r=r.slice(1)||"*"]=e[r]||[]).unshift(n):(e[r]=e[r]||[]).push(n)}}function ng(e,t,n,r){var i={},o=e===nf;function a(s){var u;return i[s]=!0,T.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function ny(e,t){var n,r,i=T.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&T.extend(!0,e,r),e}nd.href=t6.href,T.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:t6.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(t6.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":np,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":T.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?ny(ny(e,T.ajaxSettings),t):ny(T.ajaxSettings,e)},ajaxPrefilter:nh(nc),ajaxTransport:nh(nf),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var r,i,o,a,s,u,l,c,f,p,d=T.ajaxSetup({},n),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?T(h):T.event,y=T.Deferred(),m=T.Callbacks("once memory"),x=d.statusCode||{},b={},w={},C="canceled",j={readyState:0,getResponseHeader:function(e){var t;if(l){if(!a){a={};while(t=ns.exec(o))a[t[1].toLowerCase()+" "]=(a[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=a[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(e,t){return null==l&&(b[e=w[e.toLowerCase()]=w[e.toLowerCase()]||e]=t),this},overrideMimeType:function(e){return null==l&&(d.mimeType=e),this},statusCode:function(e){var t;if(e){if(l)j.always(e[j.status]);else for(t in e)x[t]=[x[t],e[t]]}return this},abort:function(e){var t=e||C;return r&&r.abort(t),E(0,t),this}};if(y.promise(j),d.url=((t||d.url||t6.href)+"").replace(nl,t6.protocol+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=(d.dataType||"*").toLowerCase().match(Q)||[""],null==d.crossDomain){u=v.createElement("a");try{u.href=d.url,u.href=u.href,d.crossDomain=nd.protocol+"//"+nd.host!=u.protocol+"//"+u.host}catch(e){d.crossDomain=!0}}if(ng(nc,d,n,j),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=T.param(d.data,d.traditional)),l)return j;for(f in(c=T.event&&d.global)&&0==T.active++&&T.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!nu.test(d.type),i=d.url.replace(no,""),d.hasContent?d.data&&d.processData&&0===(d.contentType||"").indexOf("application/x-www-form-urlencoded")&&(d.data=d.data.replace(ni,"+")):(p=d.url.slice(i.length),d.data&&(d.processData||"string"==typeof d.data)&&(i+=(t7.test(i)?"&":"?")+d.data,delete d.data),!1===d.cache&&(i=i.replace(na,"$1"),p=(t7.test(i)?"&":"?")+"_="+t8.guid+++p),d.url=i+p),d.ifModified&&(T.lastModified[i]&&j.setRequestHeader("If-Modified-Since",T.lastModified[i]),T.etag[i]&&j.setRequestHeader("If-None-Match",T.etag[i])),(d.data&&d.hasContent&&!1!==d.contentType||n.contentType)&&j.setRequestHeader("Content-Type",d.contentType),j.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+np+"; q=0.01":""):d.accepts["*"]),d.headers)j.setRequestHeader(f,d.headers[f]);if(d.beforeSend&&(!1===d.beforeSend.call(h,j,d)||l))return j.abort();if(C="abort",m.add(d.complete),j.done(d.success),j.fail(d.error),r=ng(nf,d,n,j)){if(j.readyState=1,c&&g.trigger("ajaxSend",[j,d]),l)return j;d.async&&d.timeout>0&&(s=e.setTimeout(function(){j.abort("timeout")},d.timeout));try{l=!1,r.send(b,E)}catch(e){if(l)throw e;E(-1,e)}}else E(-1,"No Transport");function E(t,n,a,u){var f,p,v,b,w,C=n;!l&&(l=!0,s&&e.clearTimeout(s),r=void 0,o=u||"",j.readyState=t>0?4:0,f=t>=200&&t<300||304===t,a&&(b=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r){for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(d,j,a)),!f&&T.inArray("script",d.dataTypes)>-1&&0>T.inArray("json",d.dataTypes)&&(d.converters["text script"]=function(){}),b=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift()){if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o])){for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}}if(!0!==a){if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}}}return{state:"success",data:t}}(d,b,j,f),f?(d.ifModified&&((w=j.getResponseHeader("Last-Modified"))&&(T.lastModified[i]=w),(w=j.getResponseHeader("etag"))&&(T.etag[i]=w)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,f=!(v=b.error))):(v=C,(t||!C)&&(C="error",t<0&&(t=0))),j.status=t,j.statusText=(n||C)+"",f?y.resolveWith(h,[p,C,j]):y.rejectWith(h,[j,C,v]),j.statusCode(x),x=void 0,c&&g.trigger(f?"ajaxSuccess":"ajaxError",[j,d,f?p:v]),m.fireWith(h,[j,C]),!c||(g.trigger("ajaxComplete",[j,d]),--T.active||T.event.trigger("ajaxStop")))}return j},getJSON:function(e,t,n){return T.get(e,t,n,"json")},getScript:function(e,t){return T.get(e,void 0,t,"script")}}),T.each(["get","post"],function(e,t){T[t]=function(e,n,r,i){return("function"==typeof n||null===n)&&(i=i||r,r=n,n=void 0),T.ajax(T.extend({url:e,type:t,dataType:i,data:n,success:r},T.isPlainObject(e)&&e))}}),T.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),T._evalUrl=function(e,t,n){return T.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,scriptAttrs:t.crossOrigin?{crossOrigin:t.crossOrigin}:void 0,converters:{"text script":function(){}},dataFilter:function(e){T.globalEval(e,t,n)}})},T.fn.extend({wrapAll:function(e){var t;return this[0]&&("function"==typeof e&&(e=e.call(this[0])),t=T(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return"function"==typeof e?this.each(function(t){T(this).wrapInner(e.call(this,t))}):this.each(function(){var t=T(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t="function"==typeof e;return this.each(function(n){T(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){T(this).replaceWith(this.childNodes)}),this}}),T.expr.pseudos.hidden=function(e){return!T.expr.pseudos.visible(e)},T.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},T.ajaxSettings.xhr=function(){return new e.XMLHttpRequest};var nv={0:200};function nm(e){return e.scriptAttrs||!e.headers&&(e.crossDomain||e.async&&0>T.inArray("json",e.dataTypes))}T.ajaxTransport(function(e){var t;return{send:function(n,r){var i,o=e.xhr();if(o.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(i in e.xhrFields)o[i]=e.xhrFields[i];for(i in e.mimeType&&o.overrideMimeType&&o.overrideMimeType(e.mimeType),e.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest"),n)o.setRequestHeader(i,n[i]);t=function(e){return function(){t&&(t=o.onload=o.onerror=o.onabort=o.ontimeout=null,"abort"===e?o.abort():"error"===e?r(o.status,o.statusText):r(nv[o.status]||o.status,o.statusText,"text"===(o.responseType||"text")?{text:o.responseText}:{binary:o.response},o.getAllResponseHeaders()))}},o.onload=t(),o.onabort=o.onerror=o.ontimeout=t("error"),t=t("abort");try{o.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),T.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},converters:{"text script":function(e){return T.globalEval(e),e}}}),T.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),nm(e)&&(e.type="GET")}),T.ajaxTransport("script",function(e){if(nm(e)){var t,n;return{send:function(r,i){t=T(" - - diff --git a/rootfs/html/fancyindex/header.html b/rootfs/html/fancyindex/header.html deleted file mode 100644 index c8e37cc41..000000000 --- a/rootfs/html/fancyindex/header.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - Directory index - - - - - - - - - - - -
-

Directory: diff --git a/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf index 990060a76..6f87d0745 100644 --- a/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf +++ b/rootfs/usr/local/nginx/conf/conf.d/npmplus.conf @@ -7,13 +7,14 @@ server { include conf.d/include/brotli.conf; include conf.d/include/force-tls.conf; include conf.d/include/tls-ciphers.conf; - include conf.d/include/block-exploits.conf; + include conf.d/include/always.conf; #ssl_certificate ; #ssl_certificate_key ; #ssl_trusted_certificate ; location / { + include conf.d/include/always.conf; alias /app/src/public/; index index.php index.html; diff --git a/rootfs/usr/local/nginx/conf/exploits.conf b/rootfs/usr/local/nginx/conf/exploits.conf index bddb4e1b8..73f23f412 100644 --- a/rootfs/usr/local/nginx/conf/exploits.conf +++ b/rootfs/usr/local/nginx/conf/exploits.conf @@ -19,4 +19,4 @@ map $http_user_agent $blocked_user_agent { "~*GPTBot" 1; "~*ChatGPT-User" 1; "~*CCBot" 1; -} \ No newline at end of file +} From 5708c55c533d919ae65b00fd4e6e88c5b29b5ede Mon Sep 17 00:00:00 2001 From: Zoey Date: Sun, 9 Jun 2024 15:50:18 +0200 Subject: [PATCH 038/137] enable human readable sizes again --- rootfs/usr/local/nginx/conf/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rootfs/usr/local/nginx/conf/nginx.conf b/rootfs/usr/local/nginx/conf/nginx.conf index eca4ac493..bdec1e012 100644 --- a/rootfs/usr/local/nginx/conf/nginx.conf +++ b/rootfs/usr/local/nginx/conf/nginx.conf @@ -100,7 +100,7 @@ http { fancyindex off; fancyindex_localtime on; fancyindex_show_path on; - fancyindex_exact_size on; + fancyindex_exact_size off; fancyindex_show_dotfiles off; fancyindex_hide_symlinks off; fancyindex_case_sensitive on; From fd4acf21f72b8b84b09cc41b25ae3344ef58e919 Mon Sep 17 00:00:00 2001 From: DavidCraftDev Date: Mon, 10 Jun 2024 16:31:13 +0200 Subject: [PATCH 039/137] Fix design on mobile and add ascending sorting arrow by @DavidCraftDev Co-Authored-By: David --- rootfs/app/fancyindex/footer.html | 14 ++++++++++++-- rootfs/app/fancyindex/header.html | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/rootfs/app/fancyindex/footer.html b/rootfs/app/fancyindex/footer.html index 9944ab719..2cdcca9e6 100644 --- a/rootfs/app/fancyindex/footer.html +++ b/rootfs/app/fancyindex/footer.html @@ -8,14 +8,14 @@ document.querySelector("h1").after(input); var items = [].slice.call(document.querySelectorAll("#list tbody tr")); function filterItems(item, filter) { - return !item.querySelector("td").textContent.trim().includes(filter); + return !item.querySelector("td").textContent.trim().toLowerCase().includes(filter); } input.addEventListener("keyup", function () { items.forEach((item) => { item.hidden = false; }); items - .filter((item) => filterItems(item, this.value.trim())) + .filter((item) => filterItems(item, this.value.trim().toLowerCase())) .forEach((item) => { item.hidden = true; }); @@ -25,6 +25,16 @@ tableDiv.id = "tableDiv"; tableDiv.appendChild(document.querySelector("table")); document.getElementById("search").after(tableDiv); + + var headers = document.querySelectorAll("th"); + headers.forEach((header) => { + var links = header.querySelectorAll("a"); + var ascendingLink = document.createElement("a"); + ascendingLink.innerHTML = " ↑ "; + ascendingLink.href = links[0].href; + links[0].removeAttribute("href"); + links[1].after(ascendingLink); + }); diff --git a/rootfs/app/fancyindex/header.html b/rootfs/app/fancyindex/header.html index c8e37cc41..8f944e348 100644 --- a/rootfs/app/fancyindex/header.html +++ b/rootfs/app/fancyindex/header.html @@ -1,4 +1,4 @@ - + Directory index @@ -109,6 +109,8 @@ border-bottom-width: 1px; border-bottom-style: solid; line-height: 2.5rem; + padding: 0.4rem; + overflow-wrap: break-word; } tr a { @@ -147,13 +149,28 @@ #box { padding-right: 1rem; padding-left: 1rem; - margin: 2rem; + margin: 0.5rem; + margin-top: 1.5rem; margin-bottom: 0rem; } th { padding-right: 0rem; } + + #search { + font-size: 16px; + margin-left: auto; + margin-right: auto; + } + + table { + font-size: medium; + } + + td { + max-width: 10rem; + } }
From c400610ca16b4c85966bebf467a0bfc2a54ac610 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:49:32 +0000 Subject: [PATCH 040/137] update alpine to 3.20.1 Signed-off-by: Zoey --- .github/workflows/caddy-fmt.yml | 2 +- .github/workflows/caddy.yml | 2 +- .github/workflows/docker.yml | 4 ++-- Caddy.Dockerfile | 6 ++---- Dockerfile | 10 +++++----- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/caddy-fmt.yml b/.github/workflows/caddy-fmt.yml index 05cc8432c..903126a90 100644 --- a/.github/workflows/caddy-fmt.yml +++ b/.github/workflows/caddy-fmt.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Read version id: version - run: echo "version=$(cat Caddy.Dockerfile | grep -wE "FROM caddy:*" | head -1 | sed "s|FROM caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT + run: echo "version=$(cat Caddy.Dockerfile | grep "^COPY --from=caddy:.*$" | head -1 | sed "s|COPY --from=caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT - name: caddy-fmt run: | docker run --rm -v ${{ github.workspace }}/Caddyfile:/etc/caddy/Caddyfile caddy:${{ steps.version.outputs.version }} caddy fmt --overwrite /etc/caddy/Caddyfile diff --git a/.github/workflows/caddy.yml b/.github/workflows/caddy.yml index f2248574b..cc116bec1 100644 --- a/.github/workflows/caddy.yml +++ b/.github/workflows/caddy.yml @@ -41,7 +41,7 @@ jobs: username: ${{ steps.un.outputs.un }} password: ${{ github.token }} - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f06f032a9..2fb95acad 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -53,7 +53,7 @@ jobs: # todo: embed version somewhere #sed -i "s|\"0.0.0\"|\"$version\"|g" src/ - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . @@ -73,7 +73,7 @@ jobs: id: pr run: echo "pr=$(echo pr-${{ github.ref_name }} | sed "s|refs/pull/:||g" | sed "s|/merge||g")" >> $GITHUB_OUTPUT - name: Build (PR) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name == 'pull_request' }} with: context: . diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index de4648e9f..93818cdd9 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,8 +1,6 @@ -FROM caddy:2.8.4 as caddy - -FROM alpine:3.20.0 +FROM alpine:3.20.1 RUN apk add --no-cache ca-certificates tzdata -COPY --from=caddy /usr/bin/caddy /usr/bin/caddy +COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/Dockerfile b/Dockerfile index a8829cf55..0d6b34737 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 @@ -22,7 +22,7 @@ RUN apk upgrade --no-cache -a && \ echo "APPSEC_FAILURE_ACTION=deny" | tee -a /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:290 +FROM zoeyvid/nginx-quic:294 SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CRS_VER=v4.3.0 @@ -30,9 +30,9 @@ ARG CRS_VER=v4.3.0 COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:388 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:14 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:14 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/curl-quic:397 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-server /usr/local/bin/valkey-server RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ From 586e64cd496bad5b9a595d5597ab1cae4f89549c Mon Sep 17 00:00:00 2001 From: Zoey Date: Tue, 11 Jun 2024 09:18:58 +0200 Subject: [PATCH 041/137] dep updates/alpine 3.20.1 Signed-off-by: Zoey --- .github/workflows/caddy-fmt.yml | 2 +- .github/workflows/caddy.yml | 2 +- .github/workflows/docker.yml | 4 ++-- Caddy.Dockerfile | 6 ++---- Dockerfile | 12 ++++++------ backend/package.json | 10 +++++----- frontend/package.json | 2 +- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.github/workflows/caddy-fmt.yml b/.github/workflows/caddy-fmt.yml index 05cc8432c..903126a90 100644 --- a/.github/workflows/caddy-fmt.yml +++ b/.github/workflows/caddy-fmt.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Read version id: version - run: echo "version=$(cat Caddy.Dockerfile | grep -wE "FROM caddy:*" | head -1 | sed "s|FROM caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT + run: echo "version=$(cat Caddy.Dockerfile | grep "^COPY --from=caddy:.*$" | head -1 | sed "s|COPY --from=caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT - name: caddy-fmt run: | docker run --rm -v ${{ github.workspace }}/Caddyfile:/etc/caddy/Caddyfile caddy:${{ steps.version.outputs.version }} caddy fmt --overwrite /etc/caddy/Caddyfile diff --git a/.github/workflows/caddy.yml b/.github/workflows/caddy.yml index f2248574b..cc116bec1 100644 --- a/.github/workflows/caddy.yml +++ b/.github/workflows/caddy.yml @@ -41,7 +41,7 @@ jobs: username: ${{ steps.un.outputs.un }} password: ${{ github.token }} - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e03d40ef0..13e341676 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -60,7 +60,7 @@ jobs: sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/package.json sed -i "s|\"0.0.0\"|\"$version\"|g" backend/package.json - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . @@ -80,7 +80,7 @@ jobs: id: pr run: echo "pr=$(echo pr-${{ github.ref_name }} | sed "s|refs/pull/:||g" | sed "s|/merge||g")" >> $GITHUB_OUTPUT - name: Build (PR) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name == 'pull_request' }} with: context: . diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index de4648e9f..93818cdd9 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,8 +1,6 @@ -FROM caddy:2.8.4 as caddy - -FROM alpine:3.20.0 +FROM alpine:3.20.1 RUN apk add --no-cache ca-certificates tzdata -COPY --from=caddy /usr/bin/caddy /usr/bin/caddy +COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/Dockerfile b/Dockerfile index e82d3ada2..6e5b181ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as frontend +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS frontend COPY frontend /build/frontend COPY global/certbot-dns-plugins.json /build/frontend/certbot-dns-plugins.json ARG NODE_ENV=production \ @@ -17,7 +17,7 @@ COPY darkmode.css /build/frontend/dist/css/darkmode.css COPY security.txt /build/frontend/dist/.well-known/security.txt -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as backend +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS backend SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY backend /build/backend COPY global/certbot-dns-plugins.json /build/backend/certbot-dns-plugins.json @@ -36,7 +36,7 @@ RUN apk upgrade --no-cache -a && \ yarn cache clean --all -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 @@ -59,14 +59,14 @@ RUN apk upgrade --no-cache -a && \ echo "APPSEC_FAILURE_ACTION=deny" | tee -a /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:290-python +FROM zoeyvid/nginx-quic:294-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CRS_VER=v4.3.0 COPY rootfs / -COPY --from=zoeyvid/certbot-docker:38 /usr/local /usr/local -COPY --from=zoeyvid/curl-quic:388 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/certbot-docker:42 /usr/local /usr/local +COPY --from=zoeyvid/curl-quic:397 /usr/local/bin/curl /usr/local/bin/curl RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ diff --git a/backend/package.json b/backend/package.json index 448157cfc..bd0aade09 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,7 +16,7 @@ "gravatar": "1.8.2", "jsonwebtoken": "9.0.2", "knex": "3.1.0", - "liquidjs": "10.13.1", + "liquidjs": "10.14.0", "lodash": "4.17.21", "moment": "2.30.1", "mysql": "2.18.1", @@ -29,11 +29,11 @@ "author": "Jamie Curnow and ZoeyVid ", "license": "MIT", "devDependencies": { - "@eslint/js": "9.4.0", - "eslint": "9.4.0", + "@eslint/js": "9.5.0", + "eslint": "9.5.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", - "globals": "15.4.0", - "prettier": "3.3.1" + "globals": "15.6.0", + "prettier": "3.3.2" } } diff --git a/frontend/package.json b/frontend/package.json index 78f3d80a3..b866b7e79 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ "mini-css-extract-plugin": "1.6.2", "moment": "2.30.1", "node-sass": "7.0.3", - "nodemon": "3.1.3", + "nodemon": "3.1.4", "numeral": "2.0.6", "sass-loader": "10.5.2", "style-loader": "4.0.0", From 9cafbd1c53ff86e0b13304966a470a7418ecf8fd Mon Sep 17 00:00:00 2001 From: Zoey Date: Tue, 11 Jun 2024 09:18:58 +0200 Subject: [PATCH 042/137] dep updates/alpine 3.20.1 Signed-off-by: Zoey --- .github/workflows/caddy-fmt.yml | 2 +- .github/workflows/caddy.yml | 2 +- .github/workflows/docker.yml | 4 +- Caddy.Dockerfile | 6 +-- Dockerfile | 78 ++++++++++++++++++--------------- backend/package.json | 10 ++--- frontend/package.json | 2 +- 7 files changed, 54 insertions(+), 50 deletions(-) diff --git a/.github/workflows/caddy-fmt.yml b/.github/workflows/caddy-fmt.yml index 05cc8432c..903126a90 100644 --- a/.github/workflows/caddy-fmt.yml +++ b/.github/workflows/caddy-fmt.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Read version id: version - run: echo "version=$(cat Caddy.Dockerfile | grep -wE "FROM caddy:*" | head -1 | sed "s|FROM caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT + run: echo "version=$(cat Caddy.Dockerfile | grep "^COPY --from=caddy:.*$" | head -1 | sed "s|COPY --from=caddy:\([0-9.]\+\).*|\1|g")" >> $GITHUB_OUTPUT - name: caddy-fmt run: | docker run --rm -v ${{ github.workspace }}/Caddyfile:/etc/caddy/Caddyfile caddy:${{ steps.version.outputs.version }} caddy fmt --overwrite /etc/caddy/Caddyfile diff --git a/.github/workflows/caddy.yml b/.github/workflows/caddy.yml index f2248574b..cc116bec1 100644 --- a/.github/workflows/caddy.yml +++ b/.github/workflows/caddy.yml @@ -41,7 +41,7 @@ jobs: username: ${{ steps.un.outputs.un }} password: ${{ github.token }} - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e03d40ef0..13e341676 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -60,7 +60,7 @@ jobs: sed -i "s|\"0.0.0\"|\"$version\"|g" frontend/package.json sed -i "s|\"0.0.0\"|\"$version\"|g" backend/package.json - name: Build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name != 'pull_request' }} with: context: . @@ -80,7 +80,7 @@ jobs: id: pr run: echo "pr=$(echo pr-${{ github.ref_name }} | sed "s|refs/pull/:||g" | sed "s|/merge||g")" >> $GITHUB_OUTPUT - name: Build (PR) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 if: ${{ github.event_name == 'pull_request' }} with: context: . diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index de4648e9f..93818cdd9 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,8 +1,6 @@ -FROM caddy:2.8.4 as caddy - -FROM alpine:3.20.0 +FROM alpine:3.20.1 RUN apk add --no-cache ca-certificates tzdata -COPY --from=caddy /usr/bin/caddy /usr/bin/caddy +COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/Dockerfile b/Dockerfile index e82d3ada2..9ecc73b94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,53 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as frontend -COPY frontend /build/frontend -COPY global/certbot-dns-plugins.json /build/frontend/certbot-dns-plugins.json +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS frontend +COPY frontend /app +COPY global/certbot-dns-plugins.json /app/certbot-dns-plugins.json ARG NODE_ENV=production \ NODE_OPTIONS=--openssl-legacy-provider -WORKDIR /build/frontend +WORKDIR /app/frontend RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs yarn git python3 py3-pip build-base && \ + apk add --no-cache ca-certificates nodejs yarn git python3 py3-pip build-base file && \ yarn global add clean-modules && \ pip install setuptools --no-cache-dir --break-system-packages && \ yarn --no-lockfile install && \ - clean-modules --yes && \ yarn --no-lockfile build && \ - yarn cache clean --all -COPY darkmode.css /build/frontend/dist/css/darkmode.css -COPY security.txt /build/frontend/dist/.well-known/security.txt + yarn cache clean --all && \ + clean-modules --yes && \ + find /app/node_modules/dist -name "*.node" -exec file {} \; +COPY darkmode.css /app/dist/css/darkmode.css +COPY security.txt /app/dist/.well-known/security.txt -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as backend +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS build-backend SHELL ["/bin/ash", "-eo", "pipefail", "-c"] -COPY backend /build/backend -COPY global/certbot-dns-plugins.json /build/backend/certbot-dns-plugins.json +COPY backend /app +COPY global/certbot-dns-plugins.json /app/certbot-dns-plugins.json ARG NODE_ENV=production \ TARGETARCH -WORKDIR /build/backend +WORKDIR /app RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates nodejs yarn && \ + apk add --no-cache ca-certificates nodejs yarn file && \ yarn global add clean-modules && \ if [ "$TARGETARCH" = "amd64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=x64 yarn install --no-lockfile; \ + npm_config_target_platform=linux npm_config_target_arch=x64 yarn install --no-lockfile && \ + for file in $(find /app/node_modules -name "*.node" -exec file {} \; | grep -v "x86-64" | sed "s|\(.*\):.*|\1|g"); do rm -v "$file"; done; \ elif [ "$TARGETARCH" = "arm64" ]; then \ - npm_config_target_platform=linux npm_config_target_arch=arm64 yarn install --no-lockfile; \ + npm_config_target_platform=linux npm_config_target_arch=arm64 yarn install --no-lockfile && \ + for file in $(find /app/node_modules -name "*.node" -exec file {} \; | grep -v "aarch64" | sed "s|\(.*\):.*|\1|g"); do rm -v "$file"; done; \ fi && \ - clean-modules --yes && \ - yarn cache clean --all + yarn cache clean --all && \ + clean-modules --yes +FROM alpine:3.20.1 AS strip-backend +COPY --from=build-backend /app /app +RUN apk upgrade --no-cache -a && \ + apk add --no-cache ca-certificates binutils file && \ + find /app/node_modules -name "*.node" -exec strip -s {} \; && \ + find /app/node_modules -name "*.node" -exec file {} \; -FROM --platform="$BUILDPLATFORM" alpine:3.20.0 as crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] - ARG CSNB_VER=v1.0.8 - WORKDIR /src RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates git build-base && \ @@ -59,15 +66,14 @@ RUN apk upgrade --no-cache -a && \ echo "APPSEC_FAILURE_ACTION=deny" | tee -a /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:290-python -SHELL ["/bin/ash", "-eo", "pipefail", "-c"] - -ARG CRS_VER=v4.3.0 +FROM zoeyvid/nginx-quic:294-python +SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / -COPY --from=zoeyvid/certbot-docker:38 /usr/local /usr/local -COPY --from=zoeyvid/curl-quic:388 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/certbot-docker:42 /usr/local /usr/local +COPY --from=zoeyvid/curl-quic:397 /usr/local/bin/curl /usr/local/bin/curl +ARG CRS_VER=v4.3.0 RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ nodejs \ @@ -90,24 +96,24 @@ RUN apk upgrade --no-cache -a && \ yarn global add nginxbeautifier && \ apk del --no-cache luarocks5.1 lua5.1-dev lua5.1-sec build-base git yarn -COPY --from=backend /build/backend /app -COPY --from=frontend /build/frontend/dist /html/frontend -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html /usr/local/nginx/conf/conf.d/include/captcha.html -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf -COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf +COPY --from=strip-backend /app /app +COPY --from=frontend /app/dist /html/frontend +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/ban.html /usr/local/nginx/conf/conf.d/include/ban.html +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templates/captcha.html /usr/local/nginx/conf/conf.d/include/captcha.html +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf /usr/local/nginx/conf/conf.d/include/crowdsec.conf +COPY --from=crowdsec /src/crowdsec-nginx-bouncer/nginx/crowdsec_nginx.conf /usr/local/nginx/conf/conf.d/include/crowdsec_nginx.conf RUN ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \ ln -s /app/password-reset.js /usr/local/bin/password-reset.js && \ ln -s /app/sqlite-vaccum.js /usr/local/bin/sqlite-vaccum.js && \ ln -s /app/index.js /usr/local/bin/index.js +LABEL com.centurylinklabs.watchtower.monitor-only="true" ENV NODE_ENV=production \ NODE_CONFIG_DIR=/data/etc/npm \ DB_SQLITE_FILE=/data/etc/npm/database.sqlite -LABEL com.centurylinklabs.watchtower.monitor-only="true" ENV PUID=0 \ PGID=0 \ NIBEP=48693 \ diff --git a/backend/package.json b/backend/package.json index 448157cfc..bd0aade09 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,7 +16,7 @@ "gravatar": "1.8.2", "jsonwebtoken": "9.0.2", "knex": "3.1.0", - "liquidjs": "10.13.1", + "liquidjs": "10.14.0", "lodash": "4.17.21", "moment": "2.30.1", "mysql": "2.18.1", @@ -29,11 +29,11 @@ "author": "Jamie Curnow and ZoeyVid ", "license": "MIT", "devDependencies": { - "@eslint/js": "9.4.0", - "eslint": "9.4.0", + "@eslint/js": "9.5.0", + "eslint": "9.5.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", - "globals": "15.4.0", - "prettier": "3.3.1" + "globals": "15.6.0", + "prettier": "3.3.2" } } diff --git a/frontend/package.json b/frontend/package.json index 78f3d80a3..b866b7e79 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ "mini-css-extract-plugin": "1.6.2", "moment": "2.30.1", "node-sass": "7.0.3", - "nodemon": "3.1.3", + "nodemon": "3.1.4", "numeral": "2.0.6", "sass-loader": "10.5.2", "style-loader": "4.0.0", From cb7077d409f8d281a18a8d9e1fe33098e2dd949f Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 22 Jun 2024 00:58:52 +0200 Subject: [PATCH 043/137] Update zoeyvid/nginx-quic Docker tag to v296 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e5b46ae0d..29d97f9a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,10 +21,10 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:294 +FROM zoeyvid/nginx-quic:296 SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / -COPY src / app/src +COPY src /app/src COPY --from=zoeyvid/curl-quic:397 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli From 38f6999f8e3544c72170d18bd9b55bb176e1b354 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 06:36:45 +0000 Subject: [PATCH 044/137] chore(deps): update zoeyvid/curl-quic docker tag to v399 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 73bed3ce0..6cbddcb89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:397 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:399 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From a2d7b02667e70a93678c682165a298da2a916907 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:22:46 +0000 Subject: [PATCH 045/137] chore(deps): update zoeyvid/nginx-quic docker tag to v297 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6cbddcb89..c59976fb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:296-python +FROM zoeyvid/nginx-quic:297-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 2983ec7b17717dd3c715d26e3b3c9892b842e181 Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 11 Jul 2024 16:13:30 +0200 Subject: [PATCH 046/137] Delete 20240711144745_change_incoming_port_to_string.js --- ...11144745_change_incoming_port_to_string.js | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 backend/migrations/20240711144745_change_incoming_port_to_string.js diff --git a/backend/migrations/20240711144745_change_incoming_port_to_string.js b/backend/migrations/20240711144745_change_incoming_port_to_string.js deleted file mode 100644 index 3d1e68661..000000000 --- a/backend/migrations/20240711144745_change_incoming_port_to_string.js +++ /dev/null @@ -1,42 +0,0 @@ -const migrate_name = 'change_incoming_port_to_string'; -const logger = require('../logger').migrate; - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return knex.schema - .alterTable('stream', (table) => { - table.string('incoming_port', 11).notNull().alter(); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); - }); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex /*, Promise */) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return knex.schema - .alterTable('stream', (table) => { - table.integer('incoming_port').notNull().unsigned().alter(); - }) - .then(function () { - logger.info('[' + migrate_name + '] stream Table altered'); - }); -}; From c6220b83795f86c2c4840aad71a56fbc79f2e112 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:11:26 +0000 Subject: [PATCH 047/137] dep updates --- Caddy.Dockerfile | 2 +- Dockerfile | 10 +++++----- src/public/tailwind.css | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index 93818cdd9..aa15630de 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20.1 +FROM alpine:3.20.2 RUN apk add --no-cache ca-certificates tzdata COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile diff --git a/Dockerfile b/Dockerfile index 1f7e95f28..d1a01b956 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.1 AS crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.20.2 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 WORKDIR /src @@ -21,14 +21,14 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:297-python +FROM zoeyvid/nginx-quic:299-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:399 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:22 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/curl-quic:404 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.4.0 RUN apk upgrade --no-cache -a && \ diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 50aa6f8da..77d0ba068 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +/*! tailwindcss v3.4.7 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 80881b1cb3aad4feac53fd4fd5be7741c1905418 Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 3 Aug 2024 17:06:43 +0200 Subject: [PATCH 048/137] make file executeable again Signed-off-by: Zoey --- rootfs/usr/local/bin/start.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 rootfs/usr/local/bin/start.sh diff --git a/rootfs/usr/local/bin/start.sh b/rootfs/usr/local/bin/start.sh old mode 100644 new mode 100755 From 3995d2bc713082611b99ed4f144c14ce301cb386 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:52:05 +0000 Subject: [PATCH 049/137] chore(deps): update zoeyvid/curl-quic docker tag to v408 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f86393e96..75bc233c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:404 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:408 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From d659db70512960be65dca7c41df8b2abab9724f5 Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 7 Aug 2024 18:52:23 +0000 Subject: [PATCH 050/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 77d0ba068..a6b4eafe3 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.7 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +/*! tailwindcss v3.4.8 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 312fa94afbf1392964cf39853fdc6c7e14cf80e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:24:40 +0000 Subject: [PATCH 051/137] chore(deps): update zoeyvid/nginx-quic docker tag to v305 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 75bc233c3..09e6a9e5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:299-python +FROM zoeyvid/nginx-quic:305-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From f869a69378d49fbfdcec7e629c1ff20812d1d525 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 8 Aug 2024 15:25:01 +0000 Subject: [PATCH 052/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index a6b4eafe3..c9897bbf0 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.8 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +/*! tailwindcss v3.4.9 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 7369b02a44ec333ede52927e33f3d2533bf12aa1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:40:17 +0000 Subject: [PATCH 053/137] chore(deps): update zoeyvid/nginx-quic docker tag to v306 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 09e6a9e5c..491a37bfe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:305-python +FROM zoeyvid/nginx-quic:306-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 8e407d4bbcbf13a4905a335d824f861f8c3857a0 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 15 Aug 2024 16:16:46 +0000 Subject: [PATCH 054/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index c9897bbf0..a337bafff 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.9 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +/*! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 41b14e120861fca757fa06749af8df25149bee9f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Aug 2024 22:42:18 +0000 Subject: [PATCH 055/137] Update zoeyvid/nginx-quic Docker tag to v307 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 491a37bfe..1acf827b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:306-python +FROM zoeyvid/nginx-quic:307-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 51ffd378dde4c4afb7bcae20bdcdb39f48b61518 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:51:04 +0000 Subject: [PATCH 056/137] chore(deps): update zoeyvid/curl-quic docker tag to v410 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1acf827b9..144c8d158 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:408 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:410 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From 5ee1e8957eb76ccd01dfeaea649e510c9850f110 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:01:27 +0000 Subject: [PATCH 057/137] chore(deps): update zoeyvid/nginx-quic docker tag to v310 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 144c8d158..8fa533b88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:307-python +FROM zoeyvid/nginx-quic:310-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 1ffbe650716db8b4b056632503edcfe487fbed7d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:59:55 +0000 Subject: [PATCH 058/137] chore(deps): update peter-evans/create-pull-request action to v7 --- .github/workflows/dependency-updates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml index 29c690252..d9a770932 100644 --- a/.github/workflows/dependency-updates.yml +++ b/.github/workflows/dependency-updates.yml @@ -25,7 +25,7 @@ jobs: sed -i "s|ARG CSNB_VER=.*|ARG CSNB_VER=$CSNB_VER|" Dockerfile echo "version=$CSNB_VER" >> $GITHUB_OUTPUT - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: signoff: true delete-branch: true @@ -51,7 +51,7 @@ jobs: sed -i "s|ARG CRS_VER=.*|ARG CRS_VER=$CRS_VER|" Dockerfile echo "version=$CRS_VER" >> $GITHUB_OUTPUT - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: signoff: true delete-branch: true From 4ebd0a2d30d7c7a4b42c0f6d909ba026d86a5a0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:10:53 +0000 Subject: [PATCH 059/137] chore(deps): update zoeyvid/nginx-quic docker tag to v312 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8fa533b88..3fe396571 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:310-python +FROM zoeyvid/nginx-quic:312-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 615ba3f35318dc41190ddfdda91e73ad071c9a50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 02:06:17 +0000 Subject: [PATCH 060/137] chore(deps): update alpine docker tag to v3.20.3 --- Caddy.Dockerfile | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index aa15630de..b7ef9b767 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 RUN apk add --no-cache ca-certificates tzdata COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile diff --git a/Dockerfile b/Dockerfile index 3fe396571..89140b94a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.2 AS crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.20.3 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 WORKDIR /src From c7e4cb2156cc0d38bd23771a67d4cc995226e3c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:53:47 +0000 Subject: [PATCH 061/137] chore(deps): update zoeyvid/curl-quic docker tag to v413 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 89140b94a..be0fb45c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:410 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:413 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From 6c224247f224524afa9ab70fcc112054114eb2f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:53:50 +0000 Subject: [PATCH 062/137] chore(deps): update zoeyvid/nginx-quic docker tag to v314 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index be0fb45c0..5f4a1211b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:312-python +FROM zoeyvid/nginx-quic:314-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 0ee10da78775f0d0db8eff5ebed01a7224eacc32 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 19:53:24 +0000 Subject: [PATCH 063/137] chore(deps): update zoeyvid/valkey-static docker tag to v29 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5f4a1211b..54c0b97c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ COPY rootfs / COPY src /app/src COPY --from=zoeyvid/curl-quic:413 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:26 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.5.0 RUN apk upgrade --no-cache -a && \ From 3c6624e82b6e524bc2141c571217c62ce01363a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:40:35 +0000 Subject: [PATCH 064/137] chore(deps): update zoeyvid/curl-quic docker tag to v414 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 54c0b97c8..d6b58c343 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:413 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:414 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From cb0f304ff06fb8259f7fc6c072f187e18350e6e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 01:48:19 +0000 Subject: [PATCH 065/137] chore(deps): update zoeyvid/valkey-static docker tag to v30 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d6b58c343..2592da17a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ COPY rootfs / COPY src /app/src COPY --from=zoeyvid/curl-quic:414 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:29 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.5.0 RUN apk upgrade --no-cache -a && \ From 1f480724e9ee131aaaf3036dcc29daf3b6af2d68 Mon Sep 17 00:00:00 2001 From: GitHub Date: Mon, 16 Sep 2024 01:48:48 +0000 Subject: [PATCH 066/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index a337bafff..be043a01a 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +/*! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 1ab9d59594661654d4eada0f31295d78d0605a5f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:17:56 +0000 Subject: [PATCH 067/137] chore(deps): update zoeyvid/curl-quic docker tag to v415 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2592da17a..498ea69d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:414 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:415 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From d5acc8c4506f654ff4a0109594ed560b9ab53e4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:00:56 +0000 Subject: [PATCH 068/137] chore(deps): update zoeyvid/nginx-quic docker tag to v335 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 498ea69d1..c48f77c5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:314-python +FROM zoeyvid/nginx-quic:335-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 921719b025dfd3a9ec689fb6a758386fdda27d40 Mon Sep 17 00:00:00 2001 From: GitHub Date: Tue, 17 Sep 2024 23:01:17 +0000 Subject: [PATCH 069/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index be043a01a..b7cfa1b8e 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -/*! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.12 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From b8448242a10923e1ccf524a13e00f951734aa19b Mon Sep 17 00:00:00 2001 From: Zoey Date: Wed, 18 Sep 2024 06:44:01 +0200 Subject: [PATCH 070/137] Update Dockerfile Signed-off-by: Zoey --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c48f77c5b..63bc8f8d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,7 @@ ARG CRS_VER=v4.5.0 RUN apk upgrade --no-cache -a && \ apk add --no-cache ca-certificates tzdata tini \ bash nano \ - openssl apache2-utils \ + logrotate apache2-utils \ lua5.1-lzlib lua5.1-socket \ coreutils grep findutils jq shadow su-exec fcgi \ luarocks5.1 lua5.1-dev lua5.1-sec build-base git \ From e97c39baffa660a51567cb6b36f072e301b174d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:44:26 +0000 Subject: [PATCH 071/137] chore(deps): update zoeyvid/curl-quic docker tag to v416 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 63bc8f8d3..e176a36f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:415 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:416 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From df254927e989741ee54007a594e6cefba304e21e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:12:59 +0000 Subject: [PATCH 072/137] chore(deps): update zoeyvid/nginx-quic docker tag to v337 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e176a36f3..14340ce62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:335-python +FROM zoeyvid/nginx-quic:337-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From d761f8cd9b3dd3081c8dcec49745cc1ca7ad19f8 Mon Sep 17 00:00:00 2001 From: Zoey Date: Wed, 2 Oct 2024 08:29:02 +0200 Subject: [PATCH 073/137] Update Dockerfile Signed-off-by: Zoey --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 14340ce62..48e91fc1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:337-python +FROM zoeyvid/nginx-quic:340-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 8ded42654e1bf60f3797f670b4a35d8be105078d Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 2 Oct 2024 06:29:24 +0000 Subject: [PATCH 074/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index b7cfa1b8e..c61a7f3ae 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.12 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 211f43c71b041406625e45662af78b817937cc34 Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 3 Oct 2024 00:12:33 +0200 Subject: [PATCH 075/137] Update Dockerfile Signed-off-by: Zoey --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48e91fc1c..e3cf25157 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ COPY rootfs / COPY src /app/src COPY --from=zoeyvid/curl-quic:416 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:30 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.5.0 RUN apk upgrade --no-cache -a && \ From 792a02a4a9b5b0d1ada95f6794084d7dd2bb1e25 Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 3 Oct 2024 00:19:43 +0200 Subject: [PATCH 076/137] Delete 20240921100301_regenerate_default_host.js --- .../20240921100301_regenerate_default_host.js | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 backend/migrations/20240921100301_regenerate_default_host.js diff --git a/backend/migrations/20240921100301_regenerate_default_host.js b/backend/migrations/20240921100301_regenerate_default_host.js deleted file mode 100644 index 01756ab80..000000000 --- a/backend/migrations/20240921100301_regenerate_default_host.js +++ /dev/null @@ -1,51 +0,0 @@ -const migrate_name = 'regenerate_default_host'; -const logger = require('../logger').migrate; -const internalNginx = require('../internal/nginx'); - -async function regenerateDefaultHost(knex) { - const row = await knex('setting').select('*').where('id', 'default-site').first(); - - if (!row) { - return Promise.resolve(); - } - - return internalNginx - .deleteConfig('default') - .then(() => { - return internalNginx.generateConfig('default', row); - }) - .then(() => { - return internalNginx.test(); - }) - .then(() => { - return internalNginx.reload(); - }); -} - -/** - * Migrate - * - * @see http://knexjs.org/#Schema - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.up = function (knex) { - logger.info('[' + migrate_name + '] Migrating Up...'); - - return regenerateDefaultHost(knex); -}; - -/** - * Undo Migrate - * - * @param {Object} knex - * @param {Promise} Promise - * @returns {Promise} - */ -exports.down = function (knex) { - logger.info('[' + migrate_name + '] Migrating Down...'); - - return regenerateDefaultHost(knex); -}; From 71abcad9f5ea7f395a0dd84234a7726b659f3529 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:21:37 +0000 Subject: [PATCH 077/137] chore(deps): update zoeyvid/curl-quic docker tag to v419 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 664e569d8..044adf859 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:416 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:419 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From c8ac0ec599409e0394767a1ffa213b1274b59bdd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:45:19 +0000 Subject: [PATCH 078/137] chore(deps): update zoeyvid/nginx-quic docker tag to v345 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 044adf859..73f519bbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:340-python +FROM zoeyvid/nginx-quic:345-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 07720a24adc6eda19a1aa37da12ba334c0ea9437 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:08:51 +0000 Subject: [PATCH 079/137] chore(deps): update zoeyvid/nginx-quic docker tag to v346 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 73f519bbe..255271758 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:345-python +FROM zoeyvid/nginx-quic:346-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 73210a740bd88f35b122b3fefc58db05d86c98a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:33:40 +0000 Subject: [PATCH 080/137] fix(deps): update dependency phpmailer/phpmailer to v6.9.2 --- src/composer.json | 2 +- src/composer.lock | 18 +++---- src/vendor/composer/installed.json | 14 +++--- src/vendor/composer/installed.php | 6 +-- src/vendor/phpmailer/phpmailer/README.md | 12 ++--- src/vendor/phpmailer/phpmailer/SECURITY.md | 4 +- src/vendor/phpmailer/phpmailer/VERSION | 2 +- src/vendor/phpmailer/phpmailer/composer.json | 3 +- .../phpmailer/phpmailer/get_oauth_token.php | 6 +-- .../phpmailer/language/phpmailer.lang-es.php | 13 +++-- .../phpmailer/language/phpmailer.lang-fr.php | 3 +- .../phpmailer/language/phpmailer.lang-ja.php | 14 ++++-- .../phpmailer/language/phpmailer.lang-ku.php | 27 +++++++++++ .../phpmailer/language/phpmailer.lang-ru.php | 22 ++++++--- .../phpmailer/language/phpmailer.lang-tr.php | 9 +++- .../phpmailer/language/phpmailer.lang-ur.php | 30 ++++++++++++ .../phpmailer/src/DSNConfigurator.php | 2 +- .../phpmailer/phpmailer/src/Exception.php | 2 +- src/vendor/phpmailer/phpmailer/src/OAuth.php | 4 +- .../phpmailer/src/OAuthTokenProvider.php | 2 +- .../phpmailer/phpmailer/src/PHPMailer.php | 48 +++++++++---------- src/vendor/phpmailer/phpmailer/src/POP3.php | 8 ++-- src/vendor/phpmailer/phpmailer/src/SMTP.php | 44 ++++++++++------- 23 files changed, 195 insertions(+), 100 deletions(-) create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php create mode 100644 src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php diff --git a/src/composer.json b/src/composer.json index 8d27178c0..66fb71a07 100644 --- a/src/composer.json +++ b/src/composer.json @@ -3,7 +3,7 @@ "description": "WebUI for NPMplus, which manages nginx and acme.sh", "type": "project", "require": { - "phpmailer/phpmailer": "6.9.1", + "phpmailer/phpmailer": "6.9.2", "ext-sqlite3": "*" }, "license": "AGPL-3.0", diff --git a/src/composer.lock b/src/composer.lock index 37f5be353..7babc0830 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8a3b27965b1dc3f7b739decdc026a61e", + "content-hash": "01f9e05a400a502d053cf40323d6361d", "packages": [ { "name": "phpmailer/phpmailer", - "version": "v6.9.1", + "version": "v6.9.2", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c", + "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c", "shasum": "" }, "require": { @@ -77,7 +77,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2" }, "funding": [ { @@ -85,18 +85,18 @@ "type": "github" } ], - "time": "2023-11-25T22:23:28+00:00" + "time": "2024-10-09T10:07:50+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "alpha", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "ext-sqlite3": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json index 2237910e9..de94d96ba 100644 --- a/src/vendor/composer/installed.json +++ b/src/vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "phpmailer/phpmailer", - "version": "v6.9.1", - "version_normalized": "6.9.1.0", + "version": "v6.9.2", + "version_normalized": "6.9.2.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", - "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c", + "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c", "shasum": "" }, "require": { @@ -42,7 +42,7 @@ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, - "time": "2023-11-25T22:23:28+00:00", + "time": "2024-10-09T10:07:50+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -74,7 +74,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2" }, "funding": [ { diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php index f35b78e59..58a989e54 100644 --- a/src/vendor/composer/installed.php +++ b/src/vendor/composer/installed.php @@ -11,9 +11,9 @@ ), 'versions' => array( 'phpmailer/phpmailer' => array( - 'pretty_version' => 'v6.9.1', - 'version' => '6.9.1.0', - 'reference' => '039de174cd9c17a8389754d3b877a2ed22743e18', + 'pretty_version' => 'v6.9.2', + 'version' => '6.9.2.0', + 'reference' => 'a7b17b42fa4887c92146243f3d2f4ccb962af17c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpmailer/phpmailer', 'aliases' => array(), diff --git a/src/vendor/phpmailer/phpmailer/README.md b/src/vendor/phpmailer/phpmailer/README.md index e3e4ecff3..8c2ec4543 100644 --- a/src/vendor/phpmailer/phpmailer/README.md +++ b/src/vendor/phpmailer/phpmailer/README.md @@ -41,13 +41,13 @@ you should look at before rolling your own. Try [SwiftMailer](https://swiftmaile , [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc. ## License -This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. +This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. ## Installation & loading PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: ```json -"phpmailer/phpmailer": "^6.9.1" +"phpmailer/phpmailer": "^6.9.2" ``` or run @@ -144,7 +144,7 @@ If you are re-using the instance (e.g. when sending to a mailing list), you may That's it. You should now be ready to use PHPMailer! ## Localization -PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: ```php //To load the French version @@ -162,9 +162,9 @@ To reduce PHPMailer's deployed code footprint, examples are not included if you Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). -You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](https://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. -If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](https://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). ## Tests [PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. @@ -213,7 +213,7 @@ use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-php See [changelog](changelog.md). ## History -- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](https://sourceforge.net/projects/phpmailer/). - [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. - Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. - Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. diff --git a/src/vendor/phpmailer/phpmailer/SECURITY.md b/src/vendor/phpmailer/phpmailer/SECURITY.md index 035a87f7b..4f34026df 100644 --- a/src/vendor/phpmailer/phpmailer/SECURITY.md +++ b/src/vendor/phpmailer/phpmailer/SECURITY.md @@ -13,13 +13,13 @@ PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. -PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it is not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). -PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. diff --git a/src/vendor/phpmailer/phpmailer/VERSION b/src/vendor/phpmailer/phpmailer/VERSION index dc3829f5e..6b9255cf0 100644 --- a/src/vendor/phpmailer/phpmailer/VERSION +++ b/src/vendor/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -6.9.1 +6.9.2 diff --git a/src/vendor/phpmailer/phpmailer/composer.json b/src/vendor/phpmailer/phpmailer/composer.json index fa170a0bb..7b008b7c5 100644 --- a/src/vendor/phpmailer/phpmailer/composer.json +++ b/src/vendor/phpmailer/phpmailer/composer.json @@ -28,7 +28,8 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true - } + }, + "lock": false }, "require": { "php": ">=5.5.0", diff --git a/src/vendor/phpmailer/phpmailer/get_oauth_token.php b/src/vendor/phpmailer/phpmailer/get_oauth_token.php index cda0445c6..0e54a00b6 100644 --- a/src/vendor/phpmailer/phpmailer/get_oauth_token.php +++ b/src/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -12,7 +12,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -36,7 +36,7 @@ * Aliases for League Provider Classes * Make sure you have added these to your composer.json and run `composer install` * Plenty to choose from here: - * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + * @see https://oauth2-client.thephpleague.com/providers/thirdparty/ */ //@see https://github.com/thephpleague/oauth2-google use League\OAuth2\Client\Provider\Google; @@ -178,5 +178,5 @@ ); //Use this to interact with an API on the users behalf //Use this to get a new access token if the old one expires - echo 'Refresh Token: ', $token->getRefreshToken(); + echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken()); } diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php index 699204187..4e74bfb7b 100644 --- a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php @@ -5,27 +5,32 @@ * @package PHPMailer * @author Matt Sturdy * @author Crystopher Glodzienski Cardoso + * @author Daniel Cruz */ $PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.'; $PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; $PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; $PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; $PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; $PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; $PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; $PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; $PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; $PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; $PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Hostentry inválido: '; +$PHPMAILER_LANG['invalid_host'] = 'Host inválido: '; $PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; $PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; $PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; $PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalle: '; $PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; $PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; -$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; -$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; -$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; -$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php index 0d367fcf8..a6d582d83 100644 --- a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -6,7 +6,6 @@ * Some French punctuation requires a thin non-breaking space (U+202F) character before it, * for example before a colon or exclamation mark. * There is one of these characters between these quotes: " " - * @see http://unicode.org/udhr/n/notes_fra.html */ $PHPMAILER_LANG['authenticate'] = 'Erreur SMTP : échec de l’authentification.'; @@ -31,7 +30,7 @@ $PHPMAILER_LANG['signing'] = 'Erreur de signature : '; $PHPMAILER_LANG['smtp_code'] = 'Code SMTP : '; $PHPMAILER_LANG['smtp_code_ex'] = 'Informations supplémentaires SMTP : '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échouée.'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échoué.'; $PHPMAILER_LANG['smtp_detail'] = 'Détails : '; $PHPMAILER_LANG['smtp_error'] = 'Erreur du serveur SMTP : '; $PHPMAILER_LANG['variable_set'] = 'Impossible d’initialiser ou de réinitialiser une variable : '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php index c76f5264c..d01869cec 100644 --- a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -3,27 +3,35 @@ /** * Japanese PHPMailer language file: refer to English translation for definitive list * @package PHPMailer - * @author Mitsuhiro Yoshida + * @author Mitsuhiro Yoshida * @author Yoshi Sakai * @author Arisophy + * @author ARAKI Musashi */ $PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['buggy_php'] = 'ご利用のバージョンのPHPには不具合があり、メッセージが破損するおそれがあります。問題の解決は以下のいずれかを行ってください。SMTPでの送信に切り替える。php.iniのmail.add_x_headerをoffにする。MacOSまたはLinuxに切り替える。PHPバージョン7.0.17以降または7.1.3以降にアップグレードする。'; $PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; $PHPMAILER_LANG['empty_message'] = 'メール本文が空です。'; $PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; $PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; $PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; $PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; $PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; $PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; $PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: '; -$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['invalid_header'] = '不正なヘッダー名またはその内容'; +$PHPMAILER_LANG['invalid_hostentry'] = '不正なホストエントリー: '; +$PHPMAILER_LANG['invalid_host'] = '不正なホスト: '; $PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; $PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; $PHPMAILER_LANG['signing'] = '署名エラー: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTPコード: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'SMTP追加情報: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。'; +$PHPMAILER_LANG['smtp_detail'] = '詳細: '; $PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: '; $PHPMAILER_LANG['variable_set'] = '変数が存在しません: '; -$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php new file mode 100644 index 000000000..cf3bda69f --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'هەڵەی SMTP : نەتوانرا کۆدەکە پشتڕاست بکرێتەوە '; +$PHPMAILER_LANG['connect_host'] = 'هەڵەی SMTP: نەتوانرا پەیوەندی بە سێرڤەرەوە بکات SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'هەڵەی SMTP: ئەو زانیاریانە قبوڵ نەکرا.'; +$PHPMAILER_LANG['empty_message'] = 'پەیامەکە بەتاڵە'; +$PHPMAILER_LANG['encoding'] = 'کۆدکردنی نەزانراو : '; +$PHPMAILER_LANG['execute'] = 'ناتوانرێت جێبەجێ بکرێت: '; +$PHPMAILER_LANG['file_access'] = 'ناتوانرێت دەستت بگات بە فایلەکە: '; +$PHPMAILER_LANG['file_open'] = 'هەڵەی پەڕگە(فایل): ناتوانرێت بکرێتەوە: '; +$PHPMAILER_LANG['from_failed'] = 'هەڵە لە ئاستی ناونیشانی نێرەر: '; +$PHPMAILER_LANG['instantiate'] = 'ناتوانرێت خزمەتگوزاری پۆستە پێشکەش بکرێت.'; +$PHPMAILER_LANG['invalid_address'] = 'نەتوانرا بنێردرێت ، چونکە ناونیشانی ئیمەیڵەکە نادروستە: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' مەیلەر پشتگیری ناکات'; +$PHPMAILER_LANG['provide_address'] = 'دەبێت ناونیشانی ئیمەیڵی لانیکەم یەک وەرگر دابین بکرێت.'; +$PHPMAILER_LANG['recipients_failed'] = ' هەڵەی SMTP: ئەم هەڵانەی خوارەوەشکستی هێنا لە ناردن بۆ هەردووکیان: '; +$PHPMAILER_LANG['signing'] = 'هەڵەی واژۆ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect()پەیوەندی شکستی هێنا .'; +$PHPMAILER_LANG['smtp_error'] = 'هەڵەی ئاستی سێرڤەری SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'ناتوانرێت بیگۆڕیت یان دوبارە بینێریتەوە: '; +$PHPMAILER_LANG['extension_missing'] = 'درێژکراوە نەماوە: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php index 8c8c5e817..8013f37c4 100644 --- a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php @@ -5,24 +5,32 @@ * @package PHPMailer * @author Alexey Chumakov * @author Foster Snowhill + * @author ProjectSoft */ -$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: не удалось пройти аутентификацию.'; +$PHPMAILER_LANG['buggy_php'] = 'В вашей версии PHP есть ошибка, которая может привести к повреждению сообщений. Чтобы исправить, переключитесь на отправку по SMTP, отключите опцию mail.add_x_header в ваш php.ini, переключитесь на MacOS или Linux или обновите PHP до версии 7.0.17+ или 7.1.3+.'; $PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; $PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; $PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; $PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; $PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; $PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; $PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; $PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; -$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; -$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; -$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; -$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; $PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['invalid_header'] = 'Неверное имя или значение заголовка'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Неверная запись хоста: '; +$PHPMAILER_LANG['invalid_host'] = 'Неверный хост: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['provide_address'] = 'Вы должны указать хотя бы один адрес электронной почты получателя.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: Ошибка следующих получателей: '; $PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_code'] = 'Код SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Дополнительная информация SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером.'; +$PHPMAILER_LANG['smtp_detail'] = 'Детали: '; $PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; $PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; -$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php index f938f8020..3c45bc1c3 100644 --- a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -11,21 +11,28 @@ */ $PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.'; +$PHPMAILER_LANG['buggy_php'] = 'PHP sürümünüz iletilerin bozulmasına neden olabilecek bir hatadan etkileniyor. Bunu düzeltmek için, SMTP kullanarak göndermeye geçin, mail.add_x_header seçeneğini devre dışı bırakın php.ini dosyanızdaki mail.add_x_header seçeneğini devre dışı bırakın, MacOS veya Linux geçin veya PHP sürümünü 7.0.17+ veya 7.1.3+ sürümüne yükseltin,'; $PHPMAILER_LANG['connect_host'] = 'SMTP Hatası: SMTP sunucusuna bağlanılamadı.'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP Hatası: Veri kabul edilmedi.'; $PHPMAILER_LANG['empty_message'] = 'Mesajın içeriği boş'; $PHPMAILER_LANG['encoding'] = 'Bilinmeyen karakter kodlama: '; $PHPMAILER_LANG['execute'] = 'Çalıştırılamadı: '; +$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: '; $PHPMAILER_LANG['file_access'] = 'Dosyaya erişilemedi: '; $PHPMAILER_LANG['file_open'] = 'Dosya Hatası: Dosya açılamadı: '; $PHPMAILER_LANG['from_failed'] = 'Belirtilen adreslere gönderme başarısız: '; $PHPMAILER_LANG['instantiate'] = 'Örnek e-posta fonksiyonu oluşturulamadı.'; $PHPMAILER_LANG['invalid_address'] = 'Geçersiz e-posta adresi: '; +$PHPMAILER_LANG['invalid_header'] = 'Geçersiz başlık adı veya değeri: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Geçersiz ana bilgisayar girişi: '; +$PHPMAILER_LANG['invalid_host'] = 'Geçersiz ana bilgisayar: '; $PHPMAILER_LANG['mailer_not_supported'] = ' e-posta kütüphanesi desteklenmiyor.'; $PHPMAILER_LANG['provide_address'] = 'En az bir alıcı e-posta adresi belirtmelisiniz.'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP Hatası: Belirtilen alıcılara ulaşılamadı: '; $PHPMAILER_LANG['signing'] = 'İmzalama hatası: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kodu: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'ek SMTP bilgileri: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısız.'; +$PHPMAILER_LANG['smtp_detail'] = 'SMTP SMTP Detayı: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: '; $PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: '; -$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: '; diff --git a/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php new file mode 100644 index 000000000..0b9de0f12 --- /dev/null +++ b/src/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php @@ -0,0 +1,30 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP خرابی: تصدیق کرنے سے قاصر۔'; +$PHPMAILER_LANG['connect_host'] = 'SMTP خرابی: سرور سے منسلک ہونے سے قاصر۔'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP خرابی: ڈیٹا قبول نہیں کیا گیا۔'; +$PHPMAILER_LANG['empty_message'] = 'پیغام کی باڈی خالی ہے۔'; +$PHPMAILER_LANG['encoding'] = 'نامعلوم انکوڈنگ: '; +$PHPMAILER_LANG['execute'] = 'عمل کرنے کے قابل نہیں '; +$PHPMAILER_LANG['file_access'] = 'فائل تک رسائی سے قاصر:'; +$PHPMAILER_LANG['file_open'] = 'فائل کی خرابی: فائل کو کھولنے سے قاصر:'; +$PHPMAILER_LANG['from_failed'] = 'درج ذیل بھیجنے والے کا پتہ ناکام ہو گیا:'; +$PHPMAILER_LANG['instantiate'] = 'میل فنکشن کی مثال بنانے سے قاصر۔'; +$PHPMAILER_LANG['invalid_address'] = 'بھیجنے سے قاصر: غلط ای میل پتہ:'; +$PHPMAILER_LANG['mailer_not_supported'] = ' میلر تعاون یافتہ نہیں ہے۔'; +$PHPMAILER_LANG['provide_address'] = 'آپ کو کم از کم ایک منزل کا ای میل پتہ فراہم کرنا چاہیے۔'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP خرابی: درج ذیل پتہ پر نہیں بھیجا جاسکا: '; +$PHPMAILER_LANG['signing'] = 'دستخط کی خرابی: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP ملنا ناکام ہوا'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP سرور کی خرابی: '; +$PHPMAILER_LANG['variable_set'] = 'متغیر سیٹ نہیں کیا جا سکا: '; +$PHPMAILER_LANG['extension_missing'] = 'ایکٹینشن موجود نہیں ہے۔ '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP سرور کوڈ: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'اضافی SMTP سرور کی معلومات:'; +$PHPMAILER_LANG['invalid_header'] = 'غلط ہیڈر کا نام یا قدر'; diff --git a/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php index 566c9618f..7058c1f05 100644 --- a/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php +++ b/src/vendor/phpmailer/phpmailer/src/DSNConfigurator.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2023 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. diff --git a/src/vendor/phpmailer/phpmailer/src/Exception.php b/src/vendor/phpmailer/phpmailer/src/Exception.php index 52eaf9515..09c1a2cfe 100644 --- a/src/vendor/phpmailer/phpmailer/src/Exception.php +++ b/src/vendor/phpmailer/phpmailer/src/Exception.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. diff --git a/src/vendor/phpmailer/phpmailer/src/OAuth.php b/src/vendor/phpmailer/phpmailer/src/OAuth.php index c1d5b7762..a7e958860 100644 --- a/src/vendor/phpmailer/phpmailer/src/OAuth.php +++ b/src/vendor/phpmailer/phpmailer/src/OAuth.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -29,7 +29,7 @@ * OAuth - OAuth2 authentication wrapper class. * Uses the oauth2-client package from the League of Extraordinary Packages. * - * @see http://oauth2-client.thephpleague.com + * @see https://oauth2-client.thephpleague.com * * @author Marcus Bointon (Synchro/coolbru) */ diff --git a/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php index 115550743..cbda1a129 100644 --- a/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php +++ b/src/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. diff --git a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php index ba4bcd472..12da10354 100644 --- a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php +++ b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -152,8 +152,7 @@ class PHPMailer * Only supported in simple alt or alt_inline message types * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. * - * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ - * @see http://kigkonsult.se/iCalcreator/ + * @see https://kigkonsult.se/iCalcreator/ * * @var string */ @@ -358,7 +357,7 @@ class PHPMailer public $AuthType = ''; /** - * SMTP SMTPXClient command attibutes + * SMTP SMTPXClient command attributes * * @var array */ @@ -468,7 +467,7 @@ class PHPMailer * Only applicable when sending via SMTP. * * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path - * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * @see https://www.postfix.org/VERP_README.html Postfix VERP info * * @var bool */ @@ -551,10 +550,10 @@ class PHPMailer * The function that handles the result of the send email action. * It is called out by send() for each email sent. * - * Value can be any php callable: http://www.php.net/is_callable + * Value can be any php callable: https://www.php.net/is_callable * * Parameters: - * bool $result result of the send action + * bool $result result of the send action * array $to email addresses of the recipients * array $cc cc email addresses * array $bcc bcc email addresses @@ -757,7 +756,7 @@ class PHPMailer * * @var string */ - const VERSION = '6.9.1'; + const VERSION = '6.9.2'; /** * Error severity: message only, continue processing. @@ -903,7 +902,7 @@ protected function edebug($str) } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { - $this->Debugoutput->debug($str); + $this->Debugoutput->debug(rtrim($str, "\r\n")); return; } @@ -1072,7 +1071,7 @@ public function addReplyTo($address, $name = '') * be modified after calling this function), addition of such addresses is delayed until send(). * Addresses that have been added already return false, but do not throw exceptions. * - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $kind One of 'to', 'cc', 'bcc', or 'Reply-To' * @param string $address The email address * @param string $name An optional username associated with the address * @@ -1212,7 +1211,7 @@ protected function addAnAddress($kind, $address, $name = '') * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. * Note that quotes in the name part are removed. * - * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation * * @param string $addrstr The address list string * @param bool $useimap Whether to use the IMAP extension to parse the list @@ -1407,7 +1406,6 @@ public static function validateAddress($address, $patternselect = null) * * IPv6 literals: 'first.last@[IPv6:a1::]' * Not all of these will necessarily work for sending! * - * @see http://squiloople.com/2009/12/20/email-address-validation/ * @copyright 2009-2010 Michael Rushton * Feel free to use and redistribute this code. But please keep this copyright notice. */ @@ -1734,9 +1732,8 @@ protected function sendmailSend($header, $body) //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html //Example problem: https://www.drupal.org/node/1057954 //PHP 5.6 workaround @@ -1901,7 +1898,7 @@ protected static function fileIsAccessible($path) /** * Send mail using the PHP mail() function. * - * @see http://www.php.net/manual/en/book.mail.php + * @see https://www.php.net/manual/en/book.mail.php * * @param string $header The message headers * @param string $body The message body @@ -1931,9 +1928,8 @@ protected function mailSend($header, $body) //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html //Example problem: https://www.drupal.org/node/1057954 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. @@ -3634,7 +3630,7 @@ public function has8bitChars($text) * without breaking lines within a character. * Adapted from a function by paravoid. * - * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * @see https://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 * * @param string $str multi-byte text to wrap encode * @param string $linebreak string to use as linefeed/end-of-line @@ -3690,7 +3686,7 @@ public function encodeQP($string) /** * Encode a string using Q encoding. * - * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * @see https://www.rfc-editor.org/rfc/rfc2047#section-4.2 * * @param string $str the text to encode * @param string $position Where the text is going to be used, see the RFC for what that means @@ -4228,7 +4224,7 @@ protected function serverHostname() $result = $_SERVER['SERVER_NAME']; } elseif (function_exists('gethostname') && gethostname() !== false) { $result = gethostname(); - } elseif (php_uname('n') !== false) { + } elseif (php_uname('n') !== '') { $result = php_uname('n'); } if (!static::isValidHost($result)) { @@ -4253,7 +4249,7 @@ public static function isValidHost($host) empty($host) || !is_string($host) || strlen($host) > 256 - || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+\])$/', $host) + || !preg_match('/^([a-z\d.-]*|\[[a-f\d:]+\])$/i', $host) ) { return false; } @@ -4267,8 +4263,8 @@ public static function isValidHost($host) //Is it a valid IPv4 address? return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } - //Is it a syntactically valid hostname (when embeded in a URL)? - return filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false; + //Is it a syntactically valid hostname (when embedded in a URL)? + return filter_var('https://' . $host, FILTER_VALIDATE_URL) !== false; } /** @@ -4679,7 +4675,7 @@ public static function filenameToType($filename) * Multi-byte-safe pathinfo replacement. * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. * - * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * @see https://www.php.net/manual/en/function.pathinfo.php#107461 * * @param string $path A filename or path, does not need to exist as a file * @param int|string $options Either a PATHINFO_* constant, diff --git a/src/vendor/phpmailer/phpmailer/src/POP3.php b/src/vendor/phpmailer/phpmailer/src/POP3.php index 7b25fdd7e..697c96126 100644 --- a/src/vendor/phpmailer/phpmailer/src/POP3.php +++ b/src/vendor/phpmailer/phpmailer/src/POP3.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -46,7 +46,7 @@ class POP3 * * @var string */ - const VERSION = '6.9.1'; + const VERSION = '6.9.2'; /** * Default POP3 port number. @@ -250,7 +250,9 @@ public function connect($host, $port = false, $tval = 30) //On Windows this will raise a PHP Warning error if the hostname doesn't exist. //Rather than suppress it with @fsockopen, capture it cleanly instead - set_error_handler([$this, 'catchWarning']); + set_error_handler(function () { + call_user_func_array([$this, 'catchWarning'], func_get_args()); + }); if (false === $port) { $port = static::DEFAULT_PORT; diff --git a/src/vendor/phpmailer/phpmailer/src/SMTP.php b/src/vendor/phpmailer/phpmailer/src/SMTP.php index 1b5b00771..5b238b527 100644 --- a/src/vendor/phpmailer/phpmailer/src/SMTP.php +++ b/src/vendor/phpmailer/phpmailer/src/SMTP.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -35,7 +35,7 @@ class SMTP * * @var string */ - const VERSION = '6.9.1'; + const VERSION = '6.9.2'; /** * SMTP line break constant. @@ -152,8 +152,8 @@ class SMTP /** * Whether to use VERP. * - * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @see http://www.postfix.org/VERP_README.html Info on VERP + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see https://www.postfix.org/VERP_README.html Info on VERP * * @var bool */ @@ -164,7 +164,7 @@ class SMTP * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. * - * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2 * * @var int */ @@ -187,12 +187,12 @@ class SMTP */ protected $smtp_transaction_id_patterns = [ 'exim' => '/[\d]{3} OK id=(.*)/', - 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', - 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', - 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/', 'Amazon_SES' => '/[\d]{3} Ok (.*)/', 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', - 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/', 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/', 'ZoneMTA' => '/[\d]{3} Message queued as (.*)/', 'Mailjet' => '/[\d]{3} OK queued as (.*)/', @@ -280,7 +280,8 @@ protected function edebug($str, $level = 0) } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { - $this->Debugoutput->debug($str); + //Remove trailing line breaks potentially added by calls to SMTP::client_send() + $this->Debugoutput->debug(rtrim($str, "\r\n")); return; } @@ -293,6 +294,7 @@ protected function edebug($str, $level = 0) switch ($this->Debugoutput) { case 'error_log': //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ error_log($str); break; case 'html': @@ -404,7 +406,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option $errstr = ''; if ($streamok) { $socket_context = stream_context_create($options); - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $connection = stream_socket_client( $host . ':' . $port, $errno, @@ -419,7 +423,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option 'Connection: stream_socket_client not available, falling back to fsockopen', self::DEBUG_CONNECTION ); - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $connection = fsockopen( $host, $port, @@ -483,7 +489,9 @@ public function startTLS() } //Begin encrypted connection - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $crypto_ok = stream_socket_enable_crypto( $this->smtp_conn, true, @@ -648,7 +656,7 @@ protected function hmac($data, $key) } //The following borrowed from - //http://php.net/manual/en/function.mhash.php#27225 + //https://www.php.net/manual/en/function.mhash.php#27225 //RFC 2104 HMAC implementation for php. //Creates an md5 HMAC. @@ -1162,7 +1170,9 @@ public function client_send($data, $command = '') } else { $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); } - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $result = fwrite($this->smtp_conn, $data); restore_error_handler(); @@ -1265,7 +1275,9 @@ protected function get_lines() while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { //Must pass vars in here as params are by reference //solution for signals inspired by https://github.com/symfony/symfony/pull/6540 - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $n = stream_select($selR, $selW, $selW, $this->Timelimit); restore_error_handler(); From ec574f33cbb16da38da17fcceeeb032b1eb94ffa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:20:43 +0000 Subject: [PATCH 081/137] chore(deps): update zoeyvid/nginx-quic docker tag to v347 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 255271758..1adc1c910 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:346-python +FROM zoeyvid/nginx-quic:347-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 2f0d6461266039d21038c5240f289fe3980910cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:56:14 +0000 Subject: [PATCH 082/137] chore(deps): update zoeyvid/curl-quic docker tag to v420 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1adc1c910..7b949be16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:419 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:420 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From ebe3d09d71f8c61aa071845f84e5888a2c342b97 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 17 Oct 2024 13:56:35 +0000 Subject: [PATCH 083/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index c61a7f3ae..39abab25c 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From 8bd3b813ab56bee7817ee9b4611d1cdb168558b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:48:41 +0000 Subject: [PATCH 084/137] Update zoeyvid/nginx-quic Docker tag to v349 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7b949be16..92df8733d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:347-python +FROM zoeyvid/nginx-quic:349-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 15ee498ccd9f844f11e8e5b03e6406dcad212310 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:04:10 +0000 Subject: [PATCH 085/137] Update zoeyvid/nginx-quic Docker tag to v350 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 92df8733d..2d5406d41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:349-python +FROM zoeyvid/nginx-quic:350-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From cbef03d2167cfc53b063dbfbafd0d47fadbbfb2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 12:44:37 +0000 Subject: [PATCH 086/137] Update zoeyvid/curl-quic Docker tag to v423 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2d5406d41..9fe1dab36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:420 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:423 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From b37a3b0eebecd4e65b4549df2b7ce2d5378b1bfb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:45:06 +0000 Subject: [PATCH 087/137] chore(deps): update zoeyvid/nginx-quic docker tag to v351 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9fe1dab36..13bf91e63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:350-python +FROM zoeyvid/nginx-quic:351-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 10b83b33b74cdb6902adcdcdb014eecd104118a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:08:28 +0000 Subject: [PATCH 088/137] chore(deps): update zoeyvid/curl-quic docker tag to v425 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 13bf91e63..73ee434c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:423 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:425 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From 74da93fe30e3176dcb9b93abebc10f68f6583717 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:20:25 +0000 Subject: [PATCH 089/137] chore(deps): update zoeyvid/nginx-quic docker tag to v352 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 73ee434c2..577a65666 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:351-python +FROM zoeyvid/nginx-quic:352-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From e8cbceaf08a66b91ca70ee9361f2f3aa1c59a45d Mon Sep 17 00:00:00 2001 From: GitHub Date: Sat, 16 Nov 2024 19:20:43 +0000 Subject: [PATCH 090/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 39abab25c..429f82685 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file From d7f59a7364b7a646a0a646db9b66e6edfd79a1fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:56:48 +0000 Subject: [PATCH 091/137] chore(deps): update zoeyvid/curl-quic docker tag to v426 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 577a65666..cfcda7e87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:425 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:426 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From bf7d1a561006ddf388c13387ef97800c914787c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:53:00 +0000 Subject: [PATCH 092/137] fix(deps): update dependency phpmailer/phpmailer to v6.9.3 --- src/composer.json | 2 +- src/composer.lock | 14 ++++++------- src/vendor/composer/installed.json | 14 ++++++------- src/vendor/composer/installed.php | 6 +++--- src/vendor/phpmailer/phpmailer/README.md | 2 +- src/vendor/phpmailer/phpmailer/VERSION | 2 +- .../phpmailer/phpmailer/src/PHPMailer.php | 20 +++++++++---------- src/vendor/phpmailer/phpmailer/src/POP3.php | 2 +- src/vendor/phpmailer/phpmailer/src/SMTP.php | 12 +++++------ 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/composer.json b/src/composer.json index 66fb71a07..8efb0bea9 100644 --- a/src/composer.json +++ b/src/composer.json @@ -3,7 +3,7 @@ "description": "WebUI for NPMplus, which manages nginx and acme.sh", "type": "project", "require": { - "phpmailer/phpmailer": "6.9.2", + "phpmailer/phpmailer": "6.9.3", "ext-sqlite3": "*" }, "license": "AGPL-3.0", diff --git a/src/composer.lock b/src/composer.lock index 7babc0830..02af40968 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "01f9e05a400a502d053cf40323d6361d", + "content-hash": "69b3b7f3c1d64824640c90ba13d43ea1", "packages": [ { "name": "phpmailer/phpmailer", - "version": "v6.9.2", + "version": "v6.9.3", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c" + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c", - "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", "shasum": "" }, "require": { @@ -77,7 +77,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" }, "funding": [ { @@ -85,7 +85,7 @@ "type": "github" } ], - "time": "2024-10-09T10:07:50+00:00" + "time": "2024-11-24T18:04:13+00:00" } ], "packages-dev": [], diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json index de94d96ba..6a697b3c2 100644 --- a/src/vendor/composer/installed.json +++ b/src/vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "phpmailer/phpmailer", - "version": "v6.9.2", - "version_normalized": "6.9.2.0", + "version": "v6.9.3", + "version_normalized": "6.9.3.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c" + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c", - "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", "shasum": "" }, "require": { @@ -42,7 +42,7 @@ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, - "time": "2024-10-09T10:07:50+00:00", + "time": "2024-11-24T18:04:13+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -74,7 +74,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" }, "funding": [ { diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php index 58a989e54..b323310fa 100644 --- a/src/vendor/composer/installed.php +++ b/src/vendor/composer/installed.php @@ -11,9 +11,9 @@ ), 'versions' => array( 'phpmailer/phpmailer' => array( - 'pretty_version' => 'v6.9.2', - 'version' => '6.9.2.0', - 'reference' => 'a7b17b42fa4887c92146243f3d2f4ccb962af17c', + 'pretty_version' => 'v6.9.3', + 'version' => '6.9.3.0', + 'reference' => '2f5c94fe7493efc213f643c23b1b1c249d40f47e', 'type' => 'library', 'install_path' => __DIR__ . '/../phpmailer/phpmailer', 'aliases' => array(), diff --git a/src/vendor/phpmailer/phpmailer/README.md b/src/vendor/phpmailer/phpmailer/README.md index 8c2ec4543..07fe8c8ab 100644 --- a/src/vendor/phpmailer/phpmailer/README.md +++ b/src/vendor/phpmailer/phpmailer/README.md @@ -162,7 +162,7 @@ To reduce PHPMailer's deployed code footprint, examples are not included if you Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). -You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](https://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](https://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailer/PHPMailerTest.php) a good reference for how to do various operations such as encryption. If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](https://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). diff --git a/src/vendor/phpmailer/phpmailer/VERSION b/src/vendor/phpmailer/phpmailer/VERSION index 6b9255cf0..5f54f91ea 100644 --- a/src/vendor/phpmailer/phpmailer/VERSION +++ b/src/vendor/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -6.9.2 +6.9.3 diff --git a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php index 12da10354..4a6077c01 100644 --- a/src/vendor/phpmailer/phpmailer/src/PHPMailer.php +++ b/src/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -253,7 +253,7 @@ class PHPMailer * You can set your own, but it must be in the format "", * as defined in RFC5322 section 3.6.4 or it will be ignored. * - * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * @see https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 * * @var string */ @@ -387,7 +387,7 @@ class PHPMailer * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual * delivery's outcome (success or failure) is not yet decided. * - * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + * @see https://www.rfc-editor.org/rfc/rfc3461.html#section-4.1 for more information about NOTIFY */ public $dsn = ''; @@ -756,7 +756,7 @@ class PHPMailer * * @var string */ - const VERSION = '6.9.2'; + const VERSION = '6.9.3'; /** * Error severity: message only, continue processing. @@ -1871,7 +1871,7 @@ protected static function isShellSafe($string) */ protected static function isPermittedPath($path) { - //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + //Matches scheme definition from https://www.rfc-editor.org/rfc/rfc3986#section-3.1 return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); } @@ -2705,7 +2705,7 @@ public function createHeader() } //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 - //https://tools.ietf.org/html/rfc5322#section-3.6.4 + //https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 if ( '' !== $this->MessageID && preg_match( @@ -4910,7 +4910,7 @@ public function DKIM_Sign($signHeader) * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. * Canonicalized headers should *always* use CRLF, regardless of mailer setting. * - * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.2 * * @param string $signHeader Header * @@ -4922,7 +4922,7 @@ public function DKIM_HeaderC($signHeader) $signHeader = static::normalizeBreaks($signHeader, self::CRLF); //Unfold header lines //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` - //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //@see https://www.rfc-editor.org/rfc/rfc5322#section-2.2 //That means this may break if you do something daft like put vertical tabs in your headers. $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); //Break headers out into an array @@ -4954,7 +4954,7 @@ public function DKIM_HeaderC($signHeader) * Uses the 'simple' algorithm from RFC6376 section 3.4.3. * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. * - * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.3 * * @param string $body Message Body * @@ -4990,7 +4990,7 @@ public function DKIM_Add($headers_line, $subject, $body) $DKIMquery = 'dns/txt'; //Query method $DKIMtime = time(); //Always sign these headers without being asked - //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1 + //Recommended list from https://www.rfc-editor.org/rfc/rfc6376#section-5.4.1 $autoSignHeaders = [ 'from', 'to', @@ -5096,7 +5096,7 @@ public function DKIM_Add($headers_line, $subject, $body) } //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag //which is appended after calculating the signature - //https://tools.ietf.org/html/rfc6376#section-3.5 + //https://www.rfc-editor.org/rfc/rfc6376#section-3.5 $dkimSignatureHeader = 'DKIM-Signature: v=1;' . ' d=' . $this->DKIM_domain . ';' . ' s=' . $this->DKIM_selector . ';' . static::$LE . diff --git a/src/vendor/phpmailer/phpmailer/src/POP3.php b/src/vendor/phpmailer/phpmailer/src/POP3.php index 697c96126..376fae2a8 100644 --- a/src/vendor/phpmailer/phpmailer/src/POP3.php +++ b/src/vendor/phpmailer/phpmailer/src/POP3.php @@ -46,7 +46,7 @@ class POP3 * * @var string */ - const VERSION = '6.9.2'; + const VERSION = '6.9.3'; /** * Default POP3 port number. diff --git a/src/vendor/phpmailer/phpmailer/src/SMTP.php b/src/vendor/phpmailer/phpmailer/src/SMTP.php index 5b238b527..b4eff4042 100644 --- a/src/vendor/phpmailer/phpmailer/src/SMTP.php +++ b/src/vendor/phpmailer/phpmailer/src/SMTP.php @@ -35,7 +35,7 @@ class SMTP * * @var string */ - const VERSION = '6.9.2'; + const VERSION = '6.9.3'; /** * SMTP line break constant. @@ -62,7 +62,7 @@ class SMTP * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, * *excluding* a trailing CRLF break. * - * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 + * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6 * * @var int */ @@ -72,7 +72,7 @@ class SMTP * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, * *including* a trailing CRLF line break. * - * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 + * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5 * * @var int */ @@ -373,7 +373,7 @@ public function connect($host, $port = null, $timeout = 30, $options = []) } //Anything other than a 220 response means something went wrong //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error - //https://tools.ietf.org/html/rfc5321#section-3.1 + //https://www.rfc-editor.org/rfc/rfc5321#section-3.1 if ($responseCode === 554) { $this->quit(); } @@ -582,7 +582,7 @@ public function authenticate( } //Send encoded username and password if ( - //Format from https://tools.ietf.org/html/rfc4616#section-2 + //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2 //We skip the first field (it's forgery), so the string starts with a null byte !$this->sendCommand( 'User & Password', @@ -795,7 +795,7 @@ public function data($msg_data) //Send the lines to the server foreach ($lines_out as $line_out) { //Dot-stuffing as per RFC5321 section 4.5.2 - //https://tools.ietf.org/html/rfc5321#section-4.5.2 + //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2 if (!empty($line_out) && $line_out[0] === '.') { $line_out = '.' . $line_out; } From 2154bb3300eb50e9f46dad7769a45176ac7b60da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:40:51 +0000 Subject: [PATCH 093/137] chore(deps): update zoeyvid/nginx-quic docker tag to v356 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cfcda7e87..2ecb5a3f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:352-python +FROM zoeyvid/nginx-quic:356-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From ca2f18ca1fad37859c8286e929cea4ade1cae0f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:40:48 +0000 Subject: [PATCH 094/137] chore(deps): update zoeyvid/curl-quic docker tag to v427 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2ecb5a3f9..3af0c4811 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:426 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:427 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From 83e8149b5c202ef8d9ace9687a86545d14a7cf6d Mon Sep 17 00:00:00 2001 From: DavidCraftDev Date: Mon, 25 Nov 2024 19:29:13 +0100 Subject: [PATCH 095/137] style(auth): enhance login page UI with improved layout and styling --- src/public/auth/login/index.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/public/auth/login/index.php b/src/public/auth/login/index.php index f747f4bed..283c59360 100644 --- a/src/public/auth/login/index.php +++ b/src/public/auth/login/index.php @@ -32,20 +32,26 @@ - -
+ +
-

NPMplus - Login

-
-
-
-
+

NPMplus - Login

+ + + + + + + + + + - + ?>">
--> + "Wrong TOTP.", default => "Please login.", }; - echo "

Note: " . $msg . "

"; + echo "

Note: " . $msg . "

"; } if (!array_key_exists("email", $_POST) || !array_key_exists("pswd", $_POST)) { login("none"); From e86081901e193a4a6fb819aefed7bde4e26872e5 Mon Sep 17 00:00:00 2001 From: GitHub Date: Mon, 25 Nov 2024 18:37:01 +0000 Subject: [PATCH 096/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 429f82685..d3b038385 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-screen{height:100vh}.w-full{width:100%}.flex-col{flex-direction:column}.content-center{align-content:center}.items-center{align-items:center}.justify-center{justify-content:center}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[hostname\:port\]{hostname:port} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file From ce5bd46e8c7d0d267fdc77b1e2d052d2a699a67b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:11:10 +0000 Subject: [PATCH 097/137] chore(deps): update zoeyvid/nginx-quic docker tag to v357 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3af0c4811..86e6f5300 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:356-python +FROM zoeyvid/nginx-quic:357-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From f3492983a7cdaaea062d5dc358a1d82c4edda3cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:39:35 +0000 Subject: [PATCH 098/137] chore(deps): update zoeyvid/nginx-quic docker tag to v359 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 86e6f5300..4ea0666c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:357-python +FROM zoeyvid/nginx-quic:359-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 618bec326cdce83f8001d8c2dc2da7f052e0dbbe Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 5 Dec 2024 13:39:58 +0000 Subject: [PATCH 099/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index d3b038385..7360734c1 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.16 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file From 4bdffc40076c029d11d5281199e1e161ebceea09 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:18:30 +0000 Subject: [PATCH 100/137] chore(deps): update zoeyvid/curl-quic docker tag to v430 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4ea0666c8..7cc6e6d6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:427 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:430 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From f2ff6a6139d568edc34327c53fd2eb8c38207d94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:23:42 +0000 Subject: [PATCH 101/137] chore(deps): update zoeyvid/valkey-static docker tag to v34 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7cc6e6d6f..75e135c31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ COPY rootfs / COPY src /app/src COPY --from=zoeyvid/curl-quic:430 /usr/local/bin/curl /usr/local/bin/curl -COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:31 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.7.0 RUN apk upgrade --no-cache -a && \ From d389af9158d858194ffaf954e156d6175f0b1c28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 04:09:52 +0000 Subject: [PATCH 102/137] chore(deps): update alpine docker tag to v3.21.0 --- Caddy.Dockerfile | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index b7ef9b767..7c73154c9 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20.3 +FROM alpine:3.21.0 RUN apk add --no-cache ca-certificates tzdata COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile diff --git a/Dockerfile b/Dockerfile index 75e135c31..08839c076 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.20.3 AS crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.21.0 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 WORKDIR /src From b1a0542728ca14e88c9dc7b1c9f340fbcd115a9f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:52:20 +0000 Subject: [PATCH 103/137] chore(deps): update zoeyvid/nginx-quic docker tag to v362 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 08839c076..9fc9570dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:359-python +FROM zoeyvid/nginx-quic:362-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 4bed5d8aa6ef8481a2b9d83d5d7f41c7bcdb2959 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:45:32 +0000 Subject: [PATCH 104/137] chore(deps): update zoeyvid/curl-quic docker tag to v431 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9fc9570dd..2b8de5fda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:430 /usr/local/bin/curl /usr/local/bin/curl +COPY --from=zoeyvid/curl-quic:431 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-server /usr/local/bin/valkey-server From b03eef67b0b4cdd8c42ecd8d79ef2e3361ad2baf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 06:12:44 +0000 Subject: [PATCH 105/137] chore(deps): update zoeyvid/nginx-quic docker tag to v368 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2b8de5fda..a7f474ca5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:362-python +FROM zoeyvid/nginx-quic:368-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 85c3bc30750ac1f86c12bc5a7a19804d2360737c Mon Sep 17 00:00:00 2001 From: Zoey Date: Sat, 14 Dec 2024 21:39:50 +0100 Subject: [PATCH 106/137] Update Dockerfile Signed-off-by: Zoey --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a7f474ca5..d2394d194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,13 +26,12 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/curl-quic:431 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.7.0 RUN apk upgrade --no-cache -a && \ - apk add --no-cache ca-certificates tzdata tini \ + apk add --no-cache ca-certificates tzdata tini curl \ bash nano \ logrotate apache2-utils \ lua5.1-lzlib lua5.1-socket \ From 1ae5b3d56f69e628329609847da32d2f3f9b7152 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:39:05 +0000 Subject: [PATCH 107/137] Update zoeyvid/nginx-quic Docker tag to v369 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d2394d194..702675a55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:368-python +FROM zoeyvid/nginx-quic:369-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 4a48b2daa83f1607ff4f9e279f2abe615f955338 Mon Sep 17 00:00:00 2001 From: GitHub Date: Tue, 24 Dec 2024 11:39:26 +0000 Subject: [PATCH 108/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 7360734c1..ea37c2d0e 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.16 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file From e4cd6fbbc21cd13b221bf080412546ebc2a6b9d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 15:23:21 +0000 Subject: [PATCH 109/137] Update zoeyvid/nginx-quic Docker tag to v371 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 702675a55..6b75a2cdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:369-python +FROM zoeyvid/nginx-quic:371-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From c147cea4a9a3a4b9ac8f32b28628285faff85131 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:13:00 +0000 Subject: [PATCH 110/137] Update zoeyvid/nginx-quic Docker tag to v372 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6b75a2cdd..e52b8621b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:371-python +FROM zoeyvid/nginx-quic:372-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 0a30808aaf8e580493d33d977178ae3efde3b025 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:12:57 +0000 Subject: [PATCH 111/137] Update alpine Docker tag to v3.21.1 --- Caddy.Dockerfile | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index 7c73154c9..65b40bca9 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21.0 +FROM alpine:3.21.1 RUN apk add --no-cache ca-certificates tzdata COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile diff --git a/Dockerfile b/Dockerfile index e52b8621b..1cb3ee29c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.21.0 AS crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.21.1 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 WORKDIR /src From 5904cc7048765c8d98b9378db32a7cf22c7601b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:39:54 +0000 Subject: [PATCH 112/137] Update zoeyvid/nginx-quic Docker tag to v373 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1cb3ee29c..58dbc1a1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:372-python +FROM zoeyvid/nginx-quic:373-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 15e0ffb16a498d7e66e0c923f29f4b6dd78ee6f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:09:32 +0000 Subject: [PATCH 113/137] chore(deps): update zoeyvid/valkey-static docker tag to v35 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 58dbc1a1c..08756ff5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,8 +26,8 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:34 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:35 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:35 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.7.0 RUN apk upgrade --no-cache -a && \ From d9bb0e0233fc53f7914554665379bce305911b95 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:30:45 +0000 Subject: [PATCH 114/137] chore(deps): update alpine docker tag to v3.21.2 --- Caddy.Dockerfile | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index 65b40bca9..358c5b777 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21.1 +FROM alpine:3.21.2 RUN apk add --no-cache ca-certificates tzdata COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile diff --git a/Dockerfile b/Dockerfile index 08756ff5c..84d89fdd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:labs -FROM --platform="$BUILDPLATFORM" alpine:3.21.1 AS crowdsec +FROM --platform="$BUILDPLATFORM" alpine:3.21.2 AS crowdsec SHELL ["/bin/ash", "-eo", "pipefail", "-c"] ARG CSNB_VER=v1.0.8 WORKDIR /src From 2580f79d027069e0dfb3c649949f912966202161 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 03:01:47 +0000 Subject: [PATCH 115/137] chore(deps): update caddy docker tag to v2.9.1 --- Caddy.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Caddy.Dockerfile b/Caddy.Dockerfile index 358c5b777..0043e6953 100644 --- a/Caddy.Dockerfile +++ b/Caddy.Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.21.2 RUN apk add --no-cache ca-certificates tzdata -COPY --from=caddy:2.8.4 /usr/bin/caddy /usr/bin/caddy +COPY --from=caddy:2.9.1 /usr/bin/caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] From c7619021d4090577c62834306493fcfa388547c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 06:01:38 +0000 Subject: [PATCH 116/137] chore(deps): update zoeyvid/nginx-quic docker tag to v375 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 84d89fdd2..22d7453ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:373-python +FROM zoeyvid/nginx-quic:375-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From d38633069a68a5696b7d706a59d2398366265363 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 06:01:43 +0000 Subject: [PATCH 117/137] chore(deps): update zoeyvid/valkey-static docker tag to v38 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22d7453ce..a84b4d76f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,8 +26,8 @@ SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src -COPY --from=zoeyvid/valkey-static:35 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli -COPY --from=zoeyvid/valkey-static:35 /usr/local/bin/valkey-server /usr/local/bin/valkey-server +COPY --from=zoeyvid/valkey-static:38 /usr/local/bin/valkey-cli /usr/local/bin/valkey-cli +COPY --from=zoeyvid/valkey-static:38 /usr/local/bin/valkey-server /usr/local/bin/valkey-server ARG CRS_VER=v4.7.0 RUN apk upgrade --no-cache -a && \ From 667b60097d147eb092f45794c1dfc77972b42374 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:39:19 +0000 Subject: [PATCH 118/137] Update zoeyvid/nginx-quic Docker tag to v376 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a84b4d76f..3fc7c2cc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:375-python +FROM zoeyvid/nginx-quic:376-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From c4167f976e17f0f3f0a7048e67f5424b33157f7d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:12:19 +0000 Subject: [PATCH 119/137] Update zoeyvid/nginx-quic Docker tag to v382 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3fc7c2cc7..1adfcc6d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:376-python +FROM zoeyvid/nginx-quic:382-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 376fc771838ce179a6a1a4cddd1b56bd15f1df20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:57:01 +0000 Subject: [PATCH 120/137] Update zoeyvid/nginx-quic Docker tag to v384 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1adfcc6d8..b7e83db55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:382-python +FROM zoeyvid/nginx-quic:384-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From d94b0cef1224482fb8e884fd8775c63d6f8f14ef Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 23 Jan 2025 09:04:15 +0100 Subject: [PATCH 121/137] update tailwind to v4 --- src/public/auth/login/index.php | 6 +++--- src/tailwind-input.css | 22 +++++++++++++++++++--- src/tailwind.config.js | 8 -------- 3 files changed, 22 insertions(+), 14 deletions(-) delete mode 100644 src/tailwind.config.js diff --git a/src/public/auth/login/index.php b/src/public/auth/login/index.php index 283c59360..d6cdaf6c7 100644 --- a/src/public/auth/login/index.php +++ b/src/public/auth/login/index.php @@ -41,13 +41,13 @@ function login($msg): void

NPMplus - Login

- + - + - + diff --git a/src/tailwind-input.css b/src/tailwind-input.css index b5c61c956..bbf45955e 100644 --- a/src/tailwind-input.css +++ b/src/tailwind-input.css @@ -1,3 +1,19 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import 'tailwindcss'; + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} diff --git a/src/tailwind.config.js b/src/tailwind.config.js deleted file mode 100644 index 8778c7111..000000000 --- a/src/tailwind.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["**/*.php"], - theme: { - extend: {}, - }, - plugins: [], -}; From 63c39e36ca3966a253a62894b1ccbdcfbb129487 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 23 Jan 2025 08:04:34 +0000 Subject: [PATCH 122/137] prettier Signed-off-by: GitHub --- src/tailwind-input.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tailwind-input.css b/src/tailwind-input.css index bbf45955e..4e4d9f9c8 100644 --- a/src/tailwind-input.css +++ b/src/tailwind-input.css @@ -1,4 +1,4 @@ -@import 'tailwindcss'; +@import "tailwindcss"; /* The default border color has changed to `currentColor` in Tailwind CSS v4, From 31ed77dcccc8b21408a9de39902df1b5c47001cc Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 23 Jan 2025 09:06:50 +0100 Subject: [PATCH 123/137] Update .gitignore Signed-off-by: Zoey --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 140555f02..2631c1409 100644 --- a/.gitignore +++ b/.gitignore @@ -297,7 +297,7 @@ dist/ downloads/ eggs/ .eggs/ -lib/ +#lib/ lib64/ parts/ sdist/ From 81a265a4e087dee9a57ef4da4d1fcbf55b016463 Mon Sep 17 00:00:00 2001 From: Zoey Date: Thu, 23 Jan 2025 09:08:06 +0100 Subject: [PATCH 124/137] Update tailwindcss-update.yml Signed-off-by: Zoey --- .github/workflows/tailwindcss-update.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tailwindcss-update.yml b/.github/workflows/tailwindcss-update.yml index 8251d9681..6655c794d 100644 --- a/.github/workflows/tailwindcss-update.yml +++ b/.github/workflows/tailwindcss-update.yml @@ -10,7 +10,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - run: cp -v src/tailwind.config.js tailwind.config.js - name: update tailwindcss (minify) uses: ZoeyVid/tailwindcss-update@main with: From 86bc5798d8fdfbe785dc1027da52b2394a075da8 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 23 Jan 2025 08:08:29 +0000 Subject: [PATCH 125/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index ea37c2d0e..1f24e1834 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1 +1,2 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.left-2\/4{left:50%}.top-2\/4{top:50%}.col-span-1{grid-column:span 1/span 1}.mx-0{margin-left:0;margin-right:0}.mb-6{margin-bottom:1.5rem}.mr-2{margin-right:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline{display:inline}.grid{display:grid}.hidden{display:none}.h-8{height:2rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.-translate-x-2\/4{--tw-translate-x:-50%}.-translate-x-2\/4,.-translate-y-2\/4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-2\/4{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.gap-y-4{row-gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-zinc-600{--tw-border-opacity:1;border-color:rgb(82 82 91/var(--tw-border-opacity,1))}.bg-\[\#296236\]{--tw-bg-opacity:1;background-color:rgb(41 98 54/var(--tw-bg-opacity,1))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-black{--tw-shadow-color:#000;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-zinc-500{--tw-ring-opacity:1;--tw-ring-color:rgb(113 113 122/var(--tw-ring-opacity,1))}.ring-offset-1{--tw-ring-offset-width:1px}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[hostname\:port\]{hostname:port}.hover\:bg-\[\#23552f\]:hover{--tw-bg-opacity:1;background-color:rgb(35 85 47/var(--tw-bg-opacity,1))}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-left:5rem;padding-right:5rem}.md\:text-right{text-align:right}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.dark\:shadow-white{--tw-shadow-color:#fff;--tw-shadow:var(--tw-shadow-colored)}.dark\:ring-zinc-200{--tw-ring-opacity:1;--tw-ring-color:rgb(228 228 231/var(--tw-ring-opacity,1))}} \ No newline at end of file +/*! tailwindcss v4.0.0 | MIT License | https://tailwindcss.com */ +@layer theme{:root{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(.971 .013 17.38);--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-300:oklch(.808 .114 19.571);--color-red-400:oklch(.704 .191 22.216);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-red-800:oklch(.444 .177 26.899);--color-red-900:oklch(.396 .141 25.723);--color-red-950:oklch(.258 .092 26.042);--color-orange-50:oklch(.98 .016 73.684);--color-orange-100:oklch(.954 .038 75.164);--color-orange-200:oklch(.901 .076 70.697);--color-orange-300:oklch(.837 .128 66.29);--color-orange-400:oklch(.75 .183 55.934);--color-orange-500:oklch(.705 .213 47.604);--color-orange-600:oklch(.646 .222 41.116);--color-orange-700:oklch(.553 .195 38.402);--color-orange-800:oklch(.47 .157 37.304);--color-orange-900:oklch(.408 .123 38.172);--color-orange-950:oklch(.266 .079 36.259);--color-amber-50:oklch(.987 .022 95.277);--color-amber-100:oklch(.962 .059 95.617);--color-amber-200:oklch(.924 .12 95.746);--color-amber-300:oklch(.879 .169 91.605);--color-amber-400:oklch(.828 .189 84.429);--color-amber-500:oklch(.769 .188 70.08);--color-amber-600:oklch(.666 .179 58.318);--color-amber-700:oklch(.555 .163 48.998);--color-amber-800:oklch(.473 .137 46.201);--color-amber-900:oklch(.414 .112 45.904);--color-amber-950:oklch(.279 .077 45.635);--color-yellow-50:oklch(.987 .026 102.212);--color-yellow-100:oklch(.973 .071 103.193);--color-yellow-200:oklch(.945 .129 101.54);--color-yellow-300:oklch(.905 .182 98.111);--color-yellow-400:oklch(.852 .199 91.936);--color-yellow-500:oklch(.795 .184 86.047);--color-yellow-600:oklch(.681 .162 75.834);--color-yellow-700:oklch(.554 .135 66.442);--color-yellow-800:oklch(.476 .114 61.907);--color-yellow-900:oklch(.421 .095 57.708);--color-yellow-950:oklch(.286 .066 53.813);--color-lime-50:oklch(.986 .031 120.757);--color-lime-100:oklch(.967 .067 122.328);--color-lime-200:oklch(.938 .127 124.321);--color-lime-300:oklch(.897 .196 126.665);--color-lime-400:oklch(.841 .238 128.85);--color-lime-500:oklch(.768 .233 130.85);--color-lime-600:oklch(.648 .2 131.684);--color-lime-700:oklch(.532 .157 131.589);--color-lime-800:oklch(.453 .124 130.933);--color-lime-900:oklch(.405 .101 131.063);--color-lime-950:oklch(.274 .072 132.109);--color-green-50:oklch(.982 .018 155.826);--color-green-100:oklch(.962 .044 156.743);--color-green-200:oklch(.925 .084 155.995);--color-green-300:oklch(.871 .15 154.449);--color-green-400:oklch(.792 .209 151.711);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-green-800:oklch(.448 .119 151.328);--color-green-900:oklch(.393 .095 152.535);--color-green-950:oklch(.266 .065 152.934);--color-emerald-50:oklch(.979 .021 166.113);--color-emerald-100:oklch(.95 .052 163.051);--color-emerald-200:oklch(.905 .093 164.15);--color-emerald-300:oklch(.845 .143 164.978);--color-emerald-400:oklch(.765 .177 163.223);--color-emerald-500:oklch(.696 .17 162.48);--color-emerald-600:oklch(.596 .145 163.225);--color-emerald-700:oklch(.508 .118 165.612);--color-emerald-800:oklch(.432 .095 166.913);--color-emerald-900:oklch(.378 .077 168.94);--color-emerald-950:oklch(.262 .051 172.552);--color-teal-50:oklch(.984 .014 180.72);--color-teal-100:oklch(.953 .051 180.801);--color-teal-200:oklch(.91 .096 180.426);--color-teal-300:oklch(.855 .138 181.071);--color-teal-400:oklch(.777 .152 181.912);--color-teal-500:oklch(.704 .14 182.503);--color-teal-600:oklch(.6 .118 184.704);--color-teal-700:oklch(.511 .096 186.391);--color-teal-800:oklch(.437 .078 188.216);--color-teal-900:oklch(.386 .063 188.416);--color-teal-950:oklch(.277 .046 192.524);--color-cyan-50:oklch(.984 .019 200.873);--color-cyan-100:oklch(.956 .045 203.388);--color-cyan-200:oklch(.917 .08 205.041);--color-cyan-300:oklch(.865 .127 207.078);--color-cyan-400:oklch(.789 .154 211.53);--color-cyan-500:oklch(.715 .143 215.221);--color-cyan-600:oklch(.609 .126 221.723);--color-cyan-700:oklch(.52 .105 223.128);--color-cyan-800:oklch(.45 .085 224.283);--color-cyan-900:oklch(.398 .07 227.392);--color-cyan-950:oklch(.302 .056 229.695);--color-sky-50:oklch(.977 .013 236.62);--color-sky-100:oklch(.951 .026 236.824);--color-sky-200:oklch(.901 .058 230.902);--color-sky-300:oklch(.828 .111 230.318);--color-sky-400:oklch(.746 .16 232.661);--color-sky-500:oklch(.685 .169 237.323);--color-sky-600:oklch(.588 .158 241.966);--color-sky-700:oklch(.5 .134 242.749);--color-sky-800:oklch(.443 .11 240.79);--color-sky-900:oklch(.391 .09 240.876);--color-sky-950:oklch(.293 .066 243.157);--color-blue-50:oklch(.97 .014 254.604);--color-blue-100:oklch(.932 .032 255.585);--color-blue-200:oklch(.882 .059 254.128);--color-blue-300:oklch(.809 .105 251.813);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-blue-800:oklch(.424 .199 265.638);--color-blue-900:oklch(.379 .146 265.522);--color-blue-950:oklch(.282 .091 267.935);--color-indigo-50:oklch(.962 .018 272.314);--color-indigo-100:oklch(.93 .034 272.788);--color-indigo-200:oklch(.87 .065 274.039);--color-indigo-300:oklch(.785 .115 274.713);--color-indigo-400:oklch(.673 .182 276.935);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-indigo-800:oklch(.398 .195 277.366);--color-indigo-900:oklch(.359 .144 278.697);--color-indigo-950:oklch(.257 .09 281.288);--color-violet-50:oklch(.969 .016 293.756);--color-violet-100:oklch(.943 .029 294.588);--color-violet-200:oklch(.894 .057 293.283);--color-violet-300:oklch(.811 .111 293.571);--color-violet-400:oklch(.702 .183 293.541);--color-violet-500:oklch(.606 .25 292.717);--color-violet-600:oklch(.541 .281 293.009);--color-violet-700:oklch(.491 .27 292.581);--color-violet-800:oklch(.432 .232 292.759);--color-violet-900:oklch(.38 .189 293.745);--color-violet-950:oklch(.283 .141 291.089);--color-purple-50:oklch(.977 .014 308.299);--color-purple-100:oklch(.946 .033 307.174);--color-purple-200:oklch(.902 .063 306.703);--color-purple-300:oklch(.827 .119 306.383);--color-purple-400:oklch(.714 .203 305.504);--color-purple-500:oklch(.627 .265 303.9);--color-purple-600:oklch(.558 .288 302.321);--color-purple-700:oklch(.496 .265 301.924);--color-purple-800:oklch(.438 .218 303.724);--color-purple-900:oklch(.381 .176 304.987);--color-purple-950:oklch(.291 .149 302.717);--color-fuchsia-50:oklch(.977 .017 320.058);--color-fuchsia-100:oklch(.952 .037 318.852);--color-fuchsia-200:oklch(.903 .076 319.62);--color-fuchsia-300:oklch(.833 .145 321.434);--color-fuchsia-400:oklch(.74 .238 322.16);--color-fuchsia-500:oklch(.667 .295 322.15);--color-fuchsia-600:oklch(.591 .293 322.896);--color-fuchsia-700:oklch(.518 .253 323.949);--color-fuchsia-800:oklch(.452 .211 324.591);--color-fuchsia-900:oklch(.401 .17 325.612);--color-fuchsia-950:oklch(.293 .136 325.661);--color-pink-50:oklch(.971 .014 343.198);--color-pink-100:oklch(.948 .028 342.258);--color-pink-200:oklch(.899 .061 343.231);--color-pink-300:oklch(.823 .12 346.018);--color-pink-400:oklch(.718 .202 349.761);--color-pink-500:oklch(.656 .241 354.308);--color-pink-600:oklch(.592 .249 .584);--color-pink-700:oklch(.525 .223 3.958);--color-pink-800:oklch(.459 .187 3.815);--color-pink-900:oklch(.408 .153 2.432);--color-pink-950:oklch(.284 .109 3.907);--color-rose-50:oklch(.969 .015 12.422);--color-rose-100:oklch(.941 .03 12.58);--color-rose-200:oklch(.892 .058 10.001);--color-rose-300:oklch(.81 .117 11.638);--color-rose-400:oklch(.712 .194 13.428);--color-rose-500:oklch(.645 .246 16.439);--color-rose-600:oklch(.586 .253 17.585);--color-rose-700:oklch(.514 .222 16.935);--color-rose-800:oklch(.455 .188 13.697);--color-rose-900:oklch(.41 .159 10.272);--color-rose-950:oklch(.271 .105 12.094);--color-slate-50:oklch(.984 .003 247.858);--color-slate-100:oklch(.968 .007 247.896);--color-slate-200:oklch(.929 .013 255.508);--color-slate-300:oklch(.869 .022 252.894);--color-slate-400:oklch(.704 .04 256.788);--color-slate-500:oklch(.554 .046 257.417);--color-slate-600:oklch(.446 .043 257.281);--color-slate-700:oklch(.372 .044 257.287);--color-slate-800:oklch(.279 .041 260.031);--color-slate-900:oklch(.208 .042 265.755);--color-slate-950:oklch(.129 .042 264.695);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-400:oklch(.707 .022 261.325);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-gray-950:oklch(.13 .028 261.692);--color-zinc-50:oklch(.985 0 0);--color-zinc-100:oklch(.967 .001 286.375);--color-zinc-200:oklch(.92 .004 286.32);--color-zinc-300:oklch(.871 .006 286.286);--color-zinc-400:oklch(.705 .015 286.067);--color-zinc-500:oklch(.552 .016 285.938);--color-zinc-600:oklch(.442 .017 285.786);--color-zinc-700:oklch(.37 .013 285.805);--color-zinc-800:oklch(.274 .006 286.033);--color-zinc-900:oklch(.21 .006 285.885);--color-zinc-950:oklch(.141 .005 285.823);--color-neutral-50:oklch(.985 0 0);--color-neutral-100:oklch(.97 0 0);--color-neutral-200:oklch(.922 0 0);--color-neutral-300:oklch(.87 0 0);--color-neutral-400:oklch(.708 0 0);--color-neutral-500:oklch(.556 0 0);--color-neutral-600:oklch(.439 0 0);--color-neutral-700:oklch(.371 0 0);--color-neutral-800:oklch(.269 0 0);--color-neutral-900:oklch(.205 0 0);--color-neutral-950:oklch(.145 0 0);--color-stone-50:oklch(.985 .001 106.423);--color-stone-100:oklch(.97 .001 106.424);--color-stone-200:oklch(.923 .003 48.717);--color-stone-300:oklch(.869 .005 56.366);--color-stone-400:oklch(.709 .01 56.259);--color-stone-500:oklch(.553 .013 58.071);--color-stone-600:oklch(.444 .011 73.639);--color-stone-700:oklch(.374 .01 67.558);--color-stone-800:oklch(.268 .007 34.298);--color-stone-900:oklch(.216 .006 56.043);--color-stone-950:oklch(.147 .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-2\/4{top:50%}.left-2\/4{left:50%}.col-span-1{grid-column:span 1/span 1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0{margin-inline:calc(var(--spacing)*0)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.block{display:block}.contents{display:contents}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing)*8)}.w-full{width:100%}.w-max{width:max-content}.border-collapse{border-collapse:collapse}.-translate-x-2\/4{--tw-translate-x:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-2\/4{--tw-translate-y:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-600{border-color:var(--color-zinc-600)}.bg-\[\#296236\]{background-color:#296236}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.underline{text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black{--tw-shadow-color:var(--color-black)}.ring-zinc-500{--tw-ring-color:var(--color-zinc-500)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.outline-hidden{outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[hostname\:port\]{hostname:port}@media (hover:hover){.hover\:bg-\[\#23552f\]:hover{background-color:#23552f}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (width>=48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-inline:calc(var(--spacing)*20)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{border-color:var(--color-zinc-300)}.dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-white{--tw-shadow-color:var(--color-white)}.dark\:ring-zinc-200{--tw-ring-color:var(--color-zinc-200)}}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file From 85fbfeab0e55cb62d89bc6232a9d9cfb8b4e6143 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:41:47 +0000 Subject: [PATCH 126/137] Update zoeyvid/nginx-quic Docker tag to v390 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b7e83db55..12bd54001 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:384-python +FROM zoeyvid/nginx-quic:390-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 60e4970df88780dce35429722668631f39ed8d1f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jan 2025 00:37:38 +0000 Subject: [PATCH 127/137] Update zoeyvid/nginx-quic Docker tag to v392 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 12bd54001..d8a478e49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:390-python +FROM zoeyvid/nginx-quic:392-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 222a42a6656c4953d8bcc61ed394e1e399c6d6ba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:40:03 +0000 Subject: [PATCH 128/137] Update zoeyvid/nginx-quic Docker tag to v393 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d8a478e49..83e0fcd6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:392-python +FROM zoeyvid/nginx-quic:393-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 91f1ff80b352a6b0c2a76b266c85c885188286dd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 04:50:00 +0000 Subject: [PATCH 129/137] chore(deps): update zoeyvid/nginx-quic docker tag to v395 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 83e0fcd6c..34bf15c61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:393-python +FROM zoeyvid/nginx-quic:395-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From ce63e479929b4f9878b8f532fbb296ec2248b204 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 05:22:19 +0000 Subject: [PATCH 130/137] chore(deps): update zoeyvid/nginx-quic docker tag to v411 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 34bf15c61..b4722bead 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:395-python +FROM zoeyvid/nginx-quic:411-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 88c77c106d04aa46079fd5868cc928a2edcab233 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 6 Feb 2025 05:22:34 +0000 Subject: [PATCH 131/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 1f24e1834..e3b6ed960 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.0.0 | MIT License | https://tailwindcss.com */ -@layer theme{:root{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(.971 .013 17.38);--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-300:oklch(.808 .114 19.571);--color-red-400:oklch(.704 .191 22.216);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-red-800:oklch(.444 .177 26.899);--color-red-900:oklch(.396 .141 25.723);--color-red-950:oklch(.258 .092 26.042);--color-orange-50:oklch(.98 .016 73.684);--color-orange-100:oklch(.954 .038 75.164);--color-orange-200:oklch(.901 .076 70.697);--color-orange-300:oklch(.837 .128 66.29);--color-orange-400:oklch(.75 .183 55.934);--color-orange-500:oklch(.705 .213 47.604);--color-orange-600:oklch(.646 .222 41.116);--color-orange-700:oklch(.553 .195 38.402);--color-orange-800:oklch(.47 .157 37.304);--color-orange-900:oklch(.408 .123 38.172);--color-orange-950:oklch(.266 .079 36.259);--color-amber-50:oklch(.987 .022 95.277);--color-amber-100:oklch(.962 .059 95.617);--color-amber-200:oklch(.924 .12 95.746);--color-amber-300:oklch(.879 .169 91.605);--color-amber-400:oklch(.828 .189 84.429);--color-amber-500:oklch(.769 .188 70.08);--color-amber-600:oklch(.666 .179 58.318);--color-amber-700:oklch(.555 .163 48.998);--color-amber-800:oklch(.473 .137 46.201);--color-amber-900:oklch(.414 .112 45.904);--color-amber-950:oklch(.279 .077 45.635);--color-yellow-50:oklch(.987 .026 102.212);--color-yellow-100:oklch(.973 .071 103.193);--color-yellow-200:oklch(.945 .129 101.54);--color-yellow-300:oklch(.905 .182 98.111);--color-yellow-400:oklch(.852 .199 91.936);--color-yellow-500:oklch(.795 .184 86.047);--color-yellow-600:oklch(.681 .162 75.834);--color-yellow-700:oklch(.554 .135 66.442);--color-yellow-800:oklch(.476 .114 61.907);--color-yellow-900:oklch(.421 .095 57.708);--color-yellow-950:oklch(.286 .066 53.813);--color-lime-50:oklch(.986 .031 120.757);--color-lime-100:oklch(.967 .067 122.328);--color-lime-200:oklch(.938 .127 124.321);--color-lime-300:oklch(.897 .196 126.665);--color-lime-400:oklch(.841 .238 128.85);--color-lime-500:oklch(.768 .233 130.85);--color-lime-600:oklch(.648 .2 131.684);--color-lime-700:oklch(.532 .157 131.589);--color-lime-800:oklch(.453 .124 130.933);--color-lime-900:oklch(.405 .101 131.063);--color-lime-950:oklch(.274 .072 132.109);--color-green-50:oklch(.982 .018 155.826);--color-green-100:oklch(.962 .044 156.743);--color-green-200:oklch(.925 .084 155.995);--color-green-300:oklch(.871 .15 154.449);--color-green-400:oklch(.792 .209 151.711);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-green-800:oklch(.448 .119 151.328);--color-green-900:oklch(.393 .095 152.535);--color-green-950:oklch(.266 .065 152.934);--color-emerald-50:oklch(.979 .021 166.113);--color-emerald-100:oklch(.95 .052 163.051);--color-emerald-200:oklch(.905 .093 164.15);--color-emerald-300:oklch(.845 .143 164.978);--color-emerald-400:oklch(.765 .177 163.223);--color-emerald-500:oklch(.696 .17 162.48);--color-emerald-600:oklch(.596 .145 163.225);--color-emerald-700:oklch(.508 .118 165.612);--color-emerald-800:oklch(.432 .095 166.913);--color-emerald-900:oklch(.378 .077 168.94);--color-emerald-950:oklch(.262 .051 172.552);--color-teal-50:oklch(.984 .014 180.72);--color-teal-100:oklch(.953 .051 180.801);--color-teal-200:oklch(.91 .096 180.426);--color-teal-300:oklch(.855 .138 181.071);--color-teal-400:oklch(.777 .152 181.912);--color-teal-500:oklch(.704 .14 182.503);--color-teal-600:oklch(.6 .118 184.704);--color-teal-700:oklch(.511 .096 186.391);--color-teal-800:oklch(.437 .078 188.216);--color-teal-900:oklch(.386 .063 188.416);--color-teal-950:oklch(.277 .046 192.524);--color-cyan-50:oklch(.984 .019 200.873);--color-cyan-100:oklch(.956 .045 203.388);--color-cyan-200:oklch(.917 .08 205.041);--color-cyan-300:oklch(.865 .127 207.078);--color-cyan-400:oklch(.789 .154 211.53);--color-cyan-500:oklch(.715 .143 215.221);--color-cyan-600:oklch(.609 .126 221.723);--color-cyan-700:oklch(.52 .105 223.128);--color-cyan-800:oklch(.45 .085 224.283);--color-cyan-900:oklch(.398 .07 227.392);--color-cyan-950:oklch(.302 .056 229.695);--color-sky-50:oklch(.977 .013 236.62);--color-sky-100:oklch(.951 .026 236.824);--color-sky-200:oklch(.901 .058 230.902);--color-sky-300:oklch(.828 .111 230.318);--color-sky-400:oklch(.746 .16 232.661);--color-sky-500:oklch(.685 .169 237.323);--color-sky-600:oklch(.588 .158 241.966);--color-sky-700:oklch(.5 .134 242.749);--color-sky-800:oklch(.443 .11 240.79);--color-sky-900:oklch(.391 .09 240.876);--color-sky-950:oklch(.293 .066 243.157);--color-blue-50:oklch(.97 .014 254.604);--color-blue-100:oklch(.932 .032 255.585);--color-blue-200:oklch(.882 .059 254.128);--color-blue-300:oklch(.809 .105 251.813);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-blue-800:oklch(.424 .199 265.638);--color-blue-900:oklch(.379 .146 265.522);--color-blue-950:oklch(.282 .091 267.935);--color-indigo-50:oklch(.962 .018 272.314);--color-indigo-100:oklch(.93 .034 272.788);--color-indigo-200:oklch(.87 .065 274.039);--color-indigo-300:oklch(.785 .115 274.713);--color-indigo-400:oklch(.673 .182 276.935);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-indigo-800:oklch(.398 .195 277.366);--color-indigo-900:oklch(.359 .144 278.697);--color-indigo-950:oklch(.257 .09 281.288);--color-violet-50:oklch(.969 .016 293.756);--color-violet-100:oklch(.943 .029 294.588);--color-violet-200:oklch(.894 .057 293.283);--color-violet-300:oklch(.811 .111 293.571);--color-violet-400:oklch(.702 .183 293.541);--color-violet-500:oklch(.606 .25 292.717);--color-violet-600:oklch(.541 .281 293.009);--color-violet-700:oklch(.491 .27 292.581);--color-violet-800:oklch(.432 .232 292.759);--color-violet-900:oklch(.38 .189 293.745);--color-violet-950:oklch(.283 .141 291.089);--color-purple-50:oklch(.977 .014 308.299);--color-purple-100:oklch(.946 .033 307.174);--color-purple-200:oklch(.902 .063 306.703);--color-purple-300:oklch(.827 .119 306.383);--color-purple-400:oklch(.714 .203 305.504);--color-purple-500:oklch(.627 .265 303.9);--color-purple-600:oklch(.558 .288 302.321);--color-purple-700:oklch(.496 .265 301.924);--color-purple-800:oklch(.438 .218 303.724);--color-purple-900:oklch(.381 .176 304.987);--color-purple-950:oklch(.291 .149 302.717);--color-fuchsia-50:oklch(.977 .017 320.058);--color-fuchsia-100:oklch(.952 .037 318.852);--color-fuchsia-200:oklch(.903 .076 319.62);--color-fuchsia-300:oklch(.833 .145 321.434);--color-fuchsia-400:oklch(.74 .238 322.16);--color-fuchsia-500:oklch(.667 .295 322.15);--color-fuchsia-600:oklch(.591 .293 322.896);--color-fuchsia-700:oklch(.518 .253 323.949);--color-fuchsia-800:oklch(.452 .211 324.591);--color-fuchsia-900:oklch(.401 .17 325.612);--color-fuchsia-950:oklch(.293 .136 325.661);--color-pink-50:oklch(.971 .014 343.198);--color-pink-100:oklch(.948 .028 342.258);--color-pink-200:oklch(.899 .061 343.231);--color-pink-300:oklch(.823 .12 346.018);--color-pink-400:oklch(.718 .202 349.761);--color-pink-500:oklch(.656 .241 354.308);--color-pink-600:oklch(.592 .249 .584);--color-pink-700:oklch(.525 .223 3.958);--color-pink-800:oklch(.459 .187 3.815);--color-pink-900:oklch(.408 .153 2.432);--color-pink-950:oklch(.284 .109 3.907);--color-rose-50:oklch(.969 .015 12.422);--color-rose-100:oklch(.941 .03 12.58);--color-rose-200:oklch(.892 .058 10.001);--color-rose-300:oklch(.81 .117 11.638);--color-rose-400:oklch(.712 .194 13.428);--color-rose-500:oklch(.645 .246 16.439);--color-rose-600:oklch(.586 .253 17.585);--color-rose-700:oklch(.514 .222 16.935);--color-rose-800:oklch(.455 .188 13.697);--color-rose-900:oklch(.41 .159 10.272);--color-rose-950:oklch(.271 .105 12.094);--color-slate-50:oklch(.984 .003 247.858);--color-slate-100:oklch(.968 .007 247.896);--color-slate-200:oklch(.929 .013 255.508);--color-slate-300:oklch(.869 .022 252.894);--color-slate-400:oklch(.704 .04 256.788);--color-slate-500:oklch(.554 .046 257.417);--color-slate-600:oklch(.446 .043 257.281);--color-slate-700:oklch(.372 .044 257.287);--color-slate-800:oklch(.279 .041 260.031);--color-slate-900:oklch(.208 .042 265.755);--color-slate-950:oklch(.129 .042 264.695);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-400:oklch(.707 .022 261.325);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-gray-950:oklch(.13 .028 261.692);--color-zinc-50:oklch(.985 0 0);--color-zinc-100:oklch(.967 .001 286.375);--color-zinc-200:oklch(.92 .004 286.32);--color-zinc-300:oklch(.871 .006 286.286);--color-zinc-400:oklch(.705 .015 286.067);--color-zinc-500:oklch(.552 .016 285.938);--color-zinc-600:oklch(.442 .017 285.786);--color-zinc-700:oklch(.37 .013 285.805);--color-zinc-800:oklch(.274 .006 286.033);--color-zinc-900:oklch(.21 .006 285.885);--color-zinc-950:oklch(.141 .005 285.823);--color-neutral-50:oklch(.985 0 0);--color-neutral-100:oklch(.97 0 0);--color-neutral-200:oklch(.922 0 0);--color-neutral-300:oklch(.87 0 0);--color-neutral-400:oklch(.708 0 0);--color-neutral-500:oklch(.556 0 0);--color-neutral-600:oklch(.439 0 0);--color-neutral-700:oklch(.371 0 0);--color-neutral-800:oklch(.269 0 0);--color-neutral-900:oklch(.205 0 0);--color-neutral-950:oklch(.145 0 0);--color-stone-50:oklch(.985 .001 106.423);--color-stone-100:oklch(.97 .001 106.424);--color-stone-200:oklch(.923 .003 48.717);--color-stone-300:oklch(.869 .005 56.366);--color-stone-400:oklch(.709 .01 56.259);--color-stone-500:oklch(.553 .013 58.071);--color-stone-600:oklch(.444 .011 73.639);--color-stone-700:oklch(.374 .01 67.558);--color-stone-800:oklch(.268 .007 34.298);--color-stone-900:oklch(.216 .006 56.043);--color-stone-950:oklch(.147 .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-2\/4{top:50%}.left-2\/4{left:50%}.col-span-1{grid-column:span 1/span 1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0{margin-inline:calc(var(--spacing)*0)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.block{display:block}.contents{display:contents}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing)*8)}.w-full{width:100%}.w-max{width:max-content}.border-collapse{border-collapse:collapse}.-translate-x-2\/4{--tw-translate-x:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-2\/4{--tw-translate-y:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-600{border-color:var(--color-zinc-600)}.bg-\[\#296236\]{background-color:#296236}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.underline{text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black{--tw-shadow-color:var(--color-black)}.ring-zinc-500{--tw-ring-color:var(--color-zinc-500)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.outline-hidden{outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[hostname\:port\]{hostname:port}@media (hover:hover){.hover\:bg-\[\#23552f\]:hover{background-color:#23552f}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (width>=48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-inline:calc(var(--spacing)*20)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{border-color:var(--color-zinc-300)}.dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-white{--tw-shadow-color:var(--color-white)}.dark\:ring-zinc-200{--tw-ring-color:var(--color-zinc-200)}}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file +/*! tailwindcss v4.0.3 | MIT License | https://tailwindcss.com */ +@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(.971 .013 17.38);--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-300:oklch(.808 .114 19.571);--color-red-400:oklch(.704 .191 22.216);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-red-800:oklch(.444 .177 26.899);--color-red-900:oklch(.396 .141 25.723);--color-red-950:oklch(.258 .092 26.042);--color-orange-50:oklch(.98 .016 73.684);--color-orange-100:oklch(.954 .038 75.164);--color-orange-200:oklch(.901 .076 70.697);--color-orange-300:oklch(.837 .128 66.29);--color-orange-400:oklch(.75 .183 55.934);--color-orange-500:oklch(.705 .213 47.604);--color-orange-600:oklch(.646 .222 41.116);--color-orange-700:oklch(.553 .195 38.402);--color-orange-800:oklch(.47 .157 37.304);--color-orange-900:oklch(.408 .123 38.172);--color-orange-950:oklch(.266 .079 36.259);--color-amber-50:oklch(.987 .022 95.277);--color-amber-100:oklch(.962 .059 95.617);--color-amber-200:oklch(.924 .12 95.746);--color-amber-300:oklch(.879 .169 91.605);--color-amber-400:oklch(.828 .189 84.429);--color-amber-500:oklch(.769 .188 70.08);--color-amber-600:oklch(.666 .179 58.318);--color-amber-700:oklch(.555 .163 48.998);--color-amber-800:oklch(.473 .137 46.201);--color-amber-900:oklch(.414 .112 45.904);--color-amber-950:oklch(.279 .077 45.635);--color-yellow-50:oklch(.987 .026 102.212);--color-yellow-100:oklch(.973 .071 103.193);--color-yellow-200:oklch(.945 .129 101.54);--color-yellow-300:oklch(.905 .182 98.111);--color-yellow-400:oklch(.852 .199 91.936);--color-yellow-500:oklch(.795 .184 86.047);--color-yellow-600:oklch(.681 .162 75.834);--color-yellow-700:oklch(.554 .135 66.442);--color-yellow-800:oklch(.476 .114 61.907);--color-yellow-900:oklch(.421 .095 57.708);--color-yellow-950:oklch(.286 .066 53.813);--color-lime-50:oklch(.986 .031 120.757);--color-lime-100:oklch(.967 .067 122.328);--color-lime-200:oklch(.938 .127 124.321);--color-lime-300:oklch(.897 .196 126.665);--color-lime-400:oklch(.841 .238 128.85);--color-lime-500:oklch(.768 .233 130.85);--color-lime-600:oklch(.648 .2 131.684);--color-lime-700:oklch(.532 .157 131.589);--color-lime-800:oklch(.453 .124 130.933);--color-lime-900:oklch(.405 .101 131.063);--color-lime-950:oklch(.274 .072 132.109);--color-green-50:oklch(.982 .018 155.826);--color-green-100:oklch(.962 .044 156.743);--color-green-200:oklch(.925 .084 155.995);--color-green-300:oklch(.871 .15 154.449);--color-green-400:oklch(.792 .209 151.711);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-green-800:oklch(.448 .119 151.328);--color-green-900:oklch(.393 .095 152.535);--color-green-950:oklch(.266 .065 152.934);--color-emerald-50:oklch(.979 .021 166.113);--color-emerald-100:oklch(.95 .052 163.051);--color-emerald-200:oklch(.905 .093 164.15);--color-emerald-300:oklch(.845 .143 164.978);--color-emerald-400:oklch(.765 .177 163.223);--color-emerald-500:oklch(.696 .17 162.48);--color-emerald-600:oklch(.596 .145 163.225);--color-emerald-700:oklch(.508 .118 165.612);--color-emerald-800:oklch(.432 .095 166.913);--color-emerald-900:oklch(.378 .077 168.94);--color-emerald-950:oklch(.262 .051 172.552);--color-teal-50:oklch(.984 .014 180.72);--color-teal-100:oklch(.953 .051 180.801);--color-teal-200:oklch(.91 .096 180.426);--color-teal-300:oklch(.855 .138 181.071);--color-teal-400:oklch(.777 .152 181.912);--color-teal-500:oklch(.704 .14 182.503);--color-teal-600:oklch(.6 .118 184.704);--color-teal-700:oklch(.511 .096 186.391);--color-teal-800:oklch(.437 .078 188.216);--color-teal-900:oklch(.386 .063 188.416);--color-teal-950:oklch(.277 .046 192.524);--color-cyan-50:oklch(.984 .019 200.873);--color-cyan-100:oklch(.956 .045 203.388);--color-cyan-200:oklch(.917 .08 205.041);--color-cyan-300:oklch(.865 .127 207.078);--color-cyan-400:oklch(.789 .154 211.53);--color-cyan-500:oklch(.715 .143 215.221);--color-cyan-600:oklch(.609 .126 221.723);--color-cyan-700:oklch(.52 .105 223.128);--color-cyan-800:oklch(.45 .085 224.283);--color-cyan-900:oklch(.398 .07 227.392);--color-cyan-950:oklch(.302 .056 229.695);--color-sky-50:oklch(.977 .013 236.62);--color-sky-100:oklch(.951 .026 236.824);--color-sky-200:oklch(.901 .058 230.902);--color-sky-300:oklch(.828 .111 230.318);--color-sky-400:oklch(.746 .16 232.661);--color-sky-500:oklch(.685 .169 237.323);--color-sky-600:oklch(.588 .158 241.966);--color-sky-700:oklch(.5 .134 242.749);--color-sky-800:oklch(.443 .11 240.79);--color-sky-900:oklch(.391 .09 240.876);--color-sky-950:oklch(.293 .066 243.157);--color-blue-50:oklch(.97 .014 254.604);--color-blue-100:oklch(.932 .032 255.585);--color-blue-200:oklch(.882 .059 254.128);--color-blue-300:oklch(.809 .105 251.813);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-blue-800:oklch(.424 .199 265.638);--color-blue-900:oklch(.379 .146 265.522);--color-blue-950:oklch(.282 .091 267.935);--color-indigo-50:oklch(.962 .018 272.314);--color-indigo-100:oklch(.93 .034 272.788);--color-indigo-200:oklch(.87 .065 274.039);--color-indigo-300:oklch(.785 .115 274.713);--color-indigo-400:oklch(.673 .182 276.935);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-indigo-800:oklch(.398 .195 277.366);--color-indigo-900:oklch(.359 .144 278.697);--color-indigo-950:oklch(.257 .09 281.288);--color-violet-50:oklch(.969 .016 293.756);--color-violet-100:oklch(.943 .029 294.588);--color-violet-200:oklch(.894 .057 293.283);--color-violet-300:oklch(.811 .111 293.571);--color-violet-400:oklch(.702 .183 293.541);--color-violet-500:oklch(.606 .25 292.717);--color-violet-600:oklch(.541 .281 293.009);--color-violet-700:oklch(.491 .27 292.581);--color-violet-800:oklch(.432 .232 292.759);--color-violet-900:oklch(.38 .189 293.745);--color-violet-950:oklch(.283 .141 291.089);--color-purple-50:oklch(.977 .014 308.299);--color-purple-100:oklch(.946 .033 307.174);--color-purple-200:oklch(.902 .063 306.703);--color-purple-300:oklch(.827 .119 306.383);--color-purple-400:oklch(.714 .203 305.504);--color-purple-500:oklch(.627 .265 303.9);--color-purple-600:oklch(.558 .288 302.321);--color-purple-700:oklch(.496 .265 301.924);--color-purple-800:oklch(.438 .218 303.724);--color-purple-900:oklch(.381 .176 304.987);--color-purple-950:oklch(.291 .149 302.717);--color-fuchsia-50:oklch(.977 .017 320.058);--color-fuchsia-100:oklch(.952 .037 318.852);--color-fuchsia-200:oklch(.903 .076 319.62);--color-fuchsia-300:oklch(.833 .145 321.434);--color-fuchsia-400:oklch(.74 .238 322.16);--color-fuchsia-500:oklch(.667 .295 322.15);--color-fuchsia-600:oklch(.591 .293 322.896);--color-fuchsia-700:oklch(.518 .253 323.949);--color-fuchsia-800:oklch(.452 .211 324.591);--color-fuchsia-900:oklch(.401 .17 325.612);--color-fuchsia-950:oklch(.293 .136 325.661);--color-pink-50:oklch(.971 .014 343.198);--color-pink-100:oklch(.948 .028 342.258);--color-pink-200:oklch(.899 .061 343.231);--color-pink-300:oklch(.823 .12 346.018);--color-pink-400:oklch(.718 .202 349.761);--color-pink-500:oklch(.656 .241 354.308);--color-pink-600:oklch(.592 .249 .584);--color-pink-700:oklch(.525 .223 3.958);--color-pink-800:oklch(.459 .187 3.815);--color-pink-900:oklch(.408 .153 2.432);--color-pink-950:oklch(.284 .109 3.907);--color-rose-50:oklch(.969 .015 12.422);--color-rose-100:oklch(.941 .03 12.58);--color-rose-200:oklch(.892 .058 10.001);--color-rose-300:oklch(.81 .117 11.638);--color-rose-400:oklch(.712 .194 13.428);--color-rose-500:oklch(.645 .246 16.439);--color-rose-600:oklch(.586 .253 17.585);--color-rose-700:oklch(.514 .222 16.935);--color-rose-800:oklch(.455 .188 13.697);--color-rose-900:oklch(.41 .159 10.272);--color-rose-950:oklch(.271 .105 12.094);--color-slate-50:oklch(.984 .003 247.858);--color-slate-100:oklch(.968 .007 247.896);--color-slate-200:oklch(.929 .013 255.508);--color-slate-300:oklch(.869 .022 252.894);--color-slate-400:oklch(.704 .04 256.788);--color-slate-500:oklch(.554 .046 257.417);--color-slate-600:oklch(.446 .043 257.281);--color-slate-700:oklch(.372 .044 257.287);--color-slate-800:oklch(.279 .041 260.031);--color-slate-900:oklch(.208 .042 265.755);--color-slate-950:oklch(.129 .042 264.695);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-400:oklch(.707 .022 261.325);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-gray-950:oklch(.13 .028 261.692);--color-zinc-50:oklch(.985 0 0);--color-zinc-100:oklch(.967 .001 286.375);--color-zinc-200:oklch(.92 .004 286.32);--color-zinc-300:oklch(.871 .006 286.286);--color-zinc-400:oklch(.705 .015 286.067);--color-zinc-500:oklch(.552 .016 285.938);--color-zinc-600:oklch(.442 .017 285.786);--color-zinc-700:oklch(.37 .013 285.805);--color-zinc-800:oklch(.274 .006 286.033);--color-zinc-900:oklch(.21 .006 285.885);--color-zinc-950:oklch(.141 .005 285.823);--color-neutral-50:oklch(.985 0 0);--color-neutral-100:oklch(.97 0 0);--color-neutral-200:oklch(.922 0 0);--color-neutral-300:oklch(.87 0 0);--color-neutral-400:oklch(.708 0 0);--color-neutral-500:oklch(.556 0 0);--color-neutral-600:oklch(.439 0 0);--color-neutral-700:oklch(.371 0 0);--color-neutral-800:oklch(.269 0 0);--color-neutral-900:oklch(.205 0 0);--color-neutral-950:oklch(.145 0 0);--color-stone-50:oklch(.985 .001 106.423);--color-stone-100:oklch(.97 .001 106.424);--color-stone-200:oklch(.923 .003 48.717);--color-stone-300:oklch(.869 .005 56.366);--color-stone-400:oklch(.709 .01 56.259);--color-stone-500:oklch(.553 .013 58.071);--color-stone-600:oklch(.444 .011 73.639);--color-stone-700:oklch(.374 .01 67.558);--color-stone-800:oklch(.268 .007 34.298);--color-stone-900:oklch(.216 .006 56.043);--color-stone-950:oklch(.147 .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-2\/4{top:50%}.left-2\/4{left:50%}.col-span-1{grid-column:span 1/span 1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0{margin-inline:calc(var(--spacing)*0)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.block{display:block}.contents{display:contents}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing)*8)}.w-full{width:100%}.w-max{width:max-content}.border-collapse{border-collapse:collapse}.-translate-x-2\/4{--tw-translate-x:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-2\/4{--tw-translate-y:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-600{border-color:var(--color-zinc-600)}.bg-\[\#296236\]{background-color:#296236}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.underline{text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black{--tw-shadow-color:var(--color-black)}.ring-zinc-500{--tw-ring-color:var(--color-zinc-500)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.outline-hidden{outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[hostname\:port\]{hostname:port}@media (hover:hover){.hover\:bg-\[\#23552f\]:hover{background-color:#23552f}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (width>=48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-inline:calc(var(--spacing)*20)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{border-color:var(--color-zinc-300)}.dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-white{--tw-shadow-color:var(--color-white)}.dark\:ring-zinc-200{--tw-ring-color:var(--color-zinc-200)}}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file From d93d8f7da7c7946621d1a51aa715df6dace8851f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 23:51:46 +0000 Subject: [PATCH 132/137] chore(deps): update zoeyvid/nginx-quic docker tag to v414 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b4722bead..070d53678 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:411-python +FROM zoeyvid/nginx-quic:414-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From cc16c9ce1e119f82e2c0a162af9490d7d45939d9 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 6 Feb 2025 23:52:05 +0000 Subject: [PATCH 133/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index e3b6ed960..8825156cb 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.0.3 | MIT License | https://tailwindcss.com */ +/*! tailwindcss v4.0.4 | MIT License | https://tailwindcss.com */ @layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(.971 .013 17.38);--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-300:oklch(.808 .114 19.571);--color-red-400:oklch(.704 .191 22.216);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-red-800:oklch(.444 .177 26.899);--color-red-900:oklch(.396 .141 25.723);--color-red-950:oklch(.258 .092 26.042);--color-orange-50:oklch(.98 .016 73.684);--color-orange-100:oklch(.954 .038 75.164);--color-orange-200:oklch(.901 .076 70.697);--color-orange-300:oklch(.837 .128 66.29);--color-orange-400:oklch(.75 .183 55.934);--color-orange-500:oklch(.705 .213 47.604);--color-orange-600:oklch(.646 .222 41.116);--color-orange-700:oklch(.553 .195 38.402);--color-orange-800:oklch(.47 .157 37.304);--color-orange-900:oklch(.408 .123 38.172);--color-orange-950:oklch(.266 .079 36.259);--color-amber-50:oklch(.987 .022 95.277);--color-amber-100:oklch(.962 .059 95.617);--color-amber-200:oklch(.924 .12 95.746);--color-amber-300:oklch(.879 .169 91.605);--color-amber-400:oklch(.828 .189 84.429);--color-amber-500:oklch(.769 .188 70.08);--color-amber-600:oklch(.666 .179 58.318);--color-amber-700:oklch(.555 .163 48.998);--color-amber-800:oklch(.473 .137 46.201);--color-amber-900:oklch(.414 .112 45.904);--color-amber-950:oklch(.279 .077 45.635);--color-yellow-50:oklch(.987 .026 102.212);--color-yellow-100:oklch(.973 .071 103.193);--color-yellow-200:oklch(.945 .129 101.54);--color-yellow-300:oklch(.905 .182 98.111);--color-yellow-400:oklch(.852 .199 91.936);--color-yellow-500:oklch(.795 .184 86.047);--color-yellow-600:oklch(.681 .162 75.834);--color-yellow-700:oklch(.554 .135 66.442);--color-yellow-800:oklch(.476 .114 61.907);--color-yellow-900:oklch(.421 .095 57.708);--color-yellow-950:oklch(.286 .066 53.813);--color-lime-50:oklch(.986 .031 120.757);--color-lime-100:oklch(.967 .067 122.328);--color-lime-200:oklch(.938 .127 124.321);--color-lime-300:oklch(.897 .196 126.665);--color-lime-400:oklch(.841 .238 128.85);--color-lime-500:oklch(.768 .233 130.85);--color-lime-600:oklch(.648 .2 131.684);--color-lime-700:oklch(.532 .157 131.589);--color-lime-800:oklch(.453 .124 130.933);--color-lime-900:oklch(.405 .101 131.063);--color-lime-950:oklch(.274 .072 132.109);--color-green-50:oklch(.982 .018 155.826);--color-green-100:oklch(.962 .044 156.743);--color-green-200:oklch(.925 .084 155.995);--color-green-300:oklch(.871 .15 154.449);--color-green-400:oklch(.792 .209 151.711);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-green-800:oklch(.448 .119 151.328);--color-green-900:oklch(.393 .095 152.535);--color-green-950:oklch(.266 .065 152.934);--color-emerald-50:oklch(.979 .021 166.113);--color-emerald-100:oklch(.95 .052 163.051);--color-emerald-200:oklch(.905 .093 164.15);--color-emerald-300:oklch(.845 .143 164.978);--color-emerald-400:oklch(.765 .177 163.223);--color-emerald-500:oklch(.696 .17 162.48);--color-emerald-600:oklch(.596 .145 163.225);--color-emerald-700:oklch(.508 .118 165.612);--color-emerald-800:oklch(.432 .095 166.913);--color-emerald-900:oklch(.378 .077 168.94);--color-emerald-950:oklch(.262 .051 172.552);--color-teal-50:oklch(.984 .014 180.72);--color-teal-100:oklch(.953 .051 180.801);--color-teal-200:oklch(.91 .096 180.426);--color-teal-300:oklch(.855 .138 181.071);--color-teal-400:oklch(.777 .152 181.912);--color-teal-500:oklch(.704 .14 182.503);--color-teal-600:oklch(.6 .118 184.704);--color-teal-700:oklch(.511 .096 186.391);--color-teal-800:oklch(.437 .078 188.216);--color-teal-900:oklch(.386 .063 188.416);--color-teal-950:oklch(.277 .046 192.524);--color-cyan-50:oklch(.984 .019 200.873);--color-cyan-100:oklch(.956 .045 203.388);--color-cyan-200:oklch(.917 .08 205.041);--color-cyan-300:oklch(.865 .127 207.078);--color-cyan-400:oklch(.789 .154 211.53);--color-cyan-500:oklch(.715 .143 215.221);--color-cyan-600:oklch(.609 .126 221.723);--color-cyan-700:oklch(.52 .105 223.128);--color-cyan-800:oklch(.45 .085 224.283);--color-cyan-900:oklch(.398 .07 227.392);--color-cyan-950:oklch(.302 .056 229.695);--color-sky-50:oklch(.977 .013 236.62);--color-sky-100:oklch(.951 .026 236.824);--color-sky-200:oklch(.901 .058 230.902);--color-sky-300:oklch(.828 .111 230.318);--color-sky-400:oklch(.746 .16 232.661);--color-sky-500:oklch(.685 .169 237.323);--color-sky-600:oklch(.588 .158 241.966);--color-sky-700:oklch(.5 .134 242.749);--color-sky-800:oklch(.443 .11 240.79);--color-sky-900:oklch(.391 .09 240.876);--color-sky-950:oklch(.293 .066 243.157);--color-blue-50:oklch(.97 .014 254.604);--color-blue-100:oklch(.932 .032 255.585);--color-blue-200:oklch(.882 .059 254.128);--color-blue-300:oklch(.809 .105 251.813);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-blue-800:oklch(.424 .199 265.638);--color-blue-900:oklch(.379 .146 265.522);--color-blue-950:oklch(.282 .091 267.935);--color-indigo-50:oklch(.962 .018 272.314);--color-indigo-100:oklch(.93 .034 272.788);--color-indigo-200:oklch(.87 .065 274.039);--color-indigo-300:oklch(.785 .115 274.713);--color-indigo-400:oklch(.673 .182 276.935);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-indigo-800:oklch(.398 .195 277.366);--color-indigo-900:oklch(.359 .144 278.697);--color-indigo-950:oklch(.257 .09 281.288);--color-violet-50:oklch(.969 .016 293.756);--color-violet-100:oklch(.943 .029 294.588);--color-violet-200:oklch(.894 .057 293.283);--color-violet-300:oklch(.811 .111 293.571);--color-violet-400:oklch(.702 .183 293.541);--color-violet-500:oklch(.606 .25 292.717);--color-violet-600:oklch(.541 .281 293.009);--color-violet-700:oklch(.491 .27 292.581);--color-violet-800:oklch(.432 .232 292.759);--color-violet-900:oklch(.38 .189 293.745);--color-violet-950:oklch(.283 .141 291.089);--color-purple-50:oklch(.977 .014 308.299);--color-purple-100:oklch(.946 .033 307.174);--color-purple-200:oklch(.902 .063 306.703);--color-purple-300:oklch(.827 .119 306.383);--color-purple-400:oklch(.714 .203 305.504);--color-purple-500:oklch(.627 .265 303.9);--color-purple-600:oklch(.558 .288 302.321);--color-purple-700:oklch(.496 .265 301.924);--color-purple-800:oklch(.438 .218 303.724);--color-purple-900:oklch(.381 .176 304.987);--color-purple-950:oklch(.291 .149 302.717);--color-fuchsia-50:oklch(.977 .017 320.058);--color-fuchsia-100:oklch(.952 .037 318.852);--color-fuchsia-200:oklch(.903 .076 319.62);--color-fuchsia-300:oklch(.833 .145 321.434);--color-fuchsia-400:oklch(.74 .238 322.16);--color-fuchsia-500:oklch(.667 .295 322.15);--color-fuchsia-600:oklch(.591 .293 322.896);--color-fuchsia-700:oklch(.518 .253 323.949);--color-fuchsia-800:oklch(.452 .211 324.591);--color-fuchsia-900:oklch(.401 .17 325.612);--color-fuchsia-950:oklch(.293 .136 325.661);--color-pink-50:oklch(.971 .014 343.198);--color-pink-100:oklch(.948 .028 342.258);--color-pink-200:oklch(.899 .061 343.231);--color-pink-300:oklch(.823 .12 346.018);--color-pink-400:oklch(.718 .202 349.761);--color-pink-500:oklch(.656 .241 354.308);--color-pink-600:oklch(.592 .249 .584);--color-pink-700:oklch(.525 .223 3.958);--color-pink-800:oklch(.459 .187 3.815);--color-pink-900:oklch(.408 .153 2.432);--color-pink-950:oklch(.284 .109 3.907);--color-rose-50:oklch(.969 .015 12.422);--color-rose-100:oklch(.941 .03 12.58);--color-rose-200:oklch(.892 .058 10.001);--color-rose-300:oklch(.81 .117 11.638);--color-rose-400:oklch(.712 .194 13.428);--color-rose-500:oklch(.645 .246 16.439);--color-rose-600:oklch(.586 .253 17.585);--color-rose-700:oklch(.514 .222 16.935);--color-rose-800:oklch(.455 .188 13.697);--color-rose-900:oklch(.41 .159 10.272);--color-rose-950:oklch(.271 .105 12.094);--color-slate-50:oklch(.984 .003 247.858);--color-slate-100:oklch(.968 .007 247.896);--color-slate-200:oklch(.929 .013 255.508);--color-slate-300:oklch(.869 .022 252.894);--color-slate-400:oklch(.704 .04 256.788);--color-slate-500:oklch(.554 .046 257.417);--color-slate-600:oklch(.446 .043 257.281);--color-slate-700:oklch(.372 .044 257.287);--color-slate-800:oklch(.279 .041 260.031);--color-slate-900:oklch(.208 .042 265.755);--color-slate-950:oklch(.129 .042 264.695);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-400:oklch(.707 .022 261.325);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-gray-950:oklch(.13 .028 261.692);--color-zinc-50:oklch(.985 0 0);--color-zinc-100:oklch(.967 .001 286.375);--color-zinc-200:oklch(.92 .004 286.32);--color-zinc-300:oklch(.871 .006 286.286);--color-zinc-400:oklch(.705 .015 286.067);--color-zinc-500:oklch(.552 .016 285.938);--color-zinc-600:oklch(.442 .017 285.786);--color-zinc-700:oklch(.37 .013 285.805);--color-zinc-800:oklch(.274 .006 286.033);--color-zinc-900:oklch(.21 .006 285.885);--color-zinc-950:oklch(.141 .005 285.823);--color-neutral-50:oklch(.985 0 0);--color-neutral-100:oklch(.97 0 0);--color-neutral-200:oklch(.922 0 0);--color-neutral-300:oklch(.87 0 0);--color-neutral-400:oklch(.708 0 0);--color-neutral-500:oklch(.556 0 0);--color-neutral-600:oklch(.439 0 0);--color-neutral-700:oklch(.371 0 0);--color-neutral-800:oklch(.269 0 0);--color-neutral-900:oklch(.205 0 0);--color-neutral-950:oklch(.145 0 0);--color-stone-50:oklch(.985 .001 106.423);--color-stone-100:oklch(.97 .001 106.424);--color-stone-200:oklch(.923 .003 48.717);--color-stone-300:oklch(.869 .005 56.366);--color-stone-400:oklch(.709 .01 56.259);--color-stone-500:oklch(.553 .013 58.071);--color-stone-600:oklch(.444 .011 73.639);--color-stone-700:oklch(.374 .01 67.558);--color-stone-800:oklch(.268 .007 34.298);--color-stone-900:oklch(.216 .006 56.043);--color-stone-950:oklch(.147 .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-2\/4{top:50%}.left-2\/4{left:50%}.col-span-1{grid-column:span 1/span 1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0{margin-inline:calc(var(--spacing)*0)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.block{display:block}.contents{display:contents}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing)*8)}.w-full{width:100%}.w-max{width:max-content}.border-collapse{border-collapse:collapse}.-translate-x-2\/4{--tw-translate-x:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-2\/4{--tw-translate-y:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-600{border-color:var(--color-zinc-600)}.bg-\[\#296236\]{background-color:#296236}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.underline{text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black{--tw-shadow-color:var(--color-black)}.ring-zinc-500{--tw-ring-color:var(--color-zinc-500)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.outline-hidden{outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[hostname\:port\]{hostname:port}@media (hover:hover){.hover\:bg-\[\#23552f\]:hover{background-color:#23552f}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (width>=48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-inline:calc(var(--spacing)*20)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{border-color:var(--color-zinc-300)}.dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-white{--tw-shadow-color:var(--color-white)}.dark\:ring-zinc-200{--tw-ring-color:var(--color-zinc-200)}}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file From c1c1bcf75f248661158025c0aadfac4c2ebff20d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 08:16:21 +0000 Subject: [PATCH 134/137] Update zoeyvid/nginx-quic Docker tag to v417 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 070d53678..7fe5fef83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:414-python +FROM zoeyvid/nginx-quic:417-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From 9711b6aaebff3b00ae761e78f8cd39ffa162a795 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:44:54 +0000 Subject: [PATCH 135/137] Update zoeyvid/nginx-quic Docker tag to v420 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7fe5fef83..7bb3d4899 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:417-python +FROM zoeyvid/nginx-quic:420-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src From b3dcd40de5fdf069c9e165f366b86ebefebe99ac Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 12 Feb 2025 01:45:13 +0000 Subject: [PATCH 136/137] tailwindcss-update Signed-off-by: GitHub --- src/public/tailwind.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/tailwind.css b/src/public/tailwind.css index 8825156cb..0a0e9db60 100644 --- a/src/public/tailwind.css +++ b/src/public/tailwind.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.0.4 | MIT License | https://tailwindcss.com */ +/*! tailwindcss v4.0.6 | MIT License | https://tailwindcss.com */ @layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(.971 .013 17.38);--color-red-100:oklch(.936 .032 17.717);--color-red-200:oklch(.885 .062 18.334);--color-red-300:oklch(.808 .114 19.571);--color-red-400:oklch(.704 .191 22.216);--color-red-500:oklch(.637 .237 25.331);--color-red-600:oklch(.577 .245 27.325);--color-red-700:oklch(.505 .213 27.518);--color-red-800:oklch(.444 .177 26.899);--color-red-900:oklch(.396 .141 25.723);--color-red-950:oklch(.258 .092 26.042);--color-orange-50:oklch(.98 .016 73.684);--color-orange-100:oklch(.954 .038 75.164);--color-orange-200:oklch(.901 .076 70.697);--color-orange-300:oklch(.837 .128 66.29);--color-orange-400:oklch(.75 .183 55.934);--color-orange-500:oklch(.705 .213 47.604);--color-orange-600:oklch(.646 .222 41.116);--color-orange-700:oklch(.553 .195 38.402);--color-orange-800:oklch(.47 .157 37.304);--color-orange-900:oklch(.408 .123 38.172);--color-orange-950:oklch(.266 .079 36.259);--color-amber-50:oklch(.987 .022 95.277);--color-amber-100:oklch(.962 .059 95.617);--color-amber-200:oklch(.924 .12 95.746);--color-amber-300:oklch(.879 .169 91.605);--color-amber-400:oklch(.828 .189 84.429);--color-amber-500:oklch(.769 .188 70.08);--color-amber-600:oklch(.666 .179 58.318);--color-amber-700:oklch(.555 .163 48.998);--color-amber-800:oklch(.473 .137 46.201);--color-amber-900:oklch(.414 .112 45.904);--color-amber-950:oklch(.279 .077 45.635);--color-yellow-50:oklch(.987 .026 102.212);--color-yellow-100:oklch(.973 .071 103.193);--color-yellow-200:oklch(.945 .129 101.54);--color-yellow-300:oklch(.905 .182 98.111);--color-yellow-400:oklch(.852 .199 91.936);--color-yellow-500:oklch(.795 .184 86.047);--color-yellow-600:oklch(.681 .162 75.834);--color-yellow-700:oklch(.554 .135 66.442);--color-yellow-800:oklch(.476 .114 61.907);--color-yellow-900:oklch(.421 .095 57.708);--color-yellow-950:oklch(.286 .066 53.813);--color-lime-50:oklch(.986 .031 120.757);--color-lime-100:oklch(.967 .067 122.328);--color-lime-200:oklch(.938 .127 124.321);--color-lime-300:oklch(.897 .196 126.665);--color-lime-400:oklch(.841 .238 128.85);--color-lime-500:oklch(.768 .233 130.85);--color-lime-600:oklch(.648 .2 131.684);--color-lime-700:oklch(.532 .157 131.589);--color-lime-800:oklch(.453 .124 130.933);--color-lime-900:oklch(.405 .101 131.063);--color-lime-950:oklch(.274 .072 132.109);--color-green-50:oklch(.982 .018 155.826);--color-green-100:oklch(.962 .044 156.743);--color-green-200:oklch(.925 .084 155.995);--color-green-300:oklch(.871 .15 154.449);--color-green-400:oklch(.792 .209 151.711);--color-green-500:oklch(.723 .219 149.579);--color-green-600:oklch(.627 .194 149.214);--color-green-700:oklch(.527 .154 150.069);--color-green-800:oklch(.448 .119 151.328);--color-green-900:oklch(.393 .095 152.535);--color-green-950:oklch(.266 .065 152.934);--color-emerald-50:oklch(.979 .021 166.113);--color-emerald-100:oklch(.95 .052 163.051);--color-emerald-200:oklch(.905 .093 164.15);--color-emerald-300:oklch(.845 .143 164.978);--color-emerald-400:oklch(.765 .177 163.223);--color-emerald-500:oklch(.696 .17 162.48);--color-emerald-600:oklch(.596 .145 163.225);--color-emerald-700:oklch(.508 .118 165.612);--color-emerald-800:oklch(.432 .095 166.913);--color-emerald-900:oklch(.378 .077 168.94);--color-emerald-950:oklch(.262 .051 172.552);--color-teal-50:oklch(.984 .014 180.72);--color-teal-100:oklch(.953 .051 180.801);--color-teal-200:oklch(.91 .096 180.426);--color-teal-300:oklch(.855 .138 181.071);--color-teal-400:oklch(.777 .152 181.912);--color-teal-500:oklch(.704 .14 182.503);--color-teal-600:oklch(.6 .118 184.704);--color-teal-700:oklch(.511 .096 186.391);--color-teal-800:oklch(.437 .078 188.216);--color-teal-900:oklch(.386 .063 188.416);--color-teal-950:oklch(.277 .046 192.524);--color-cyan-50:oklch(.984 .019 200.873);--color-cyan-100:oklch(.956 .045 203.388);--color-cyan-200:oklch(.917 .08 205.041);--color-cyan-300:oklch(.865 .127 207.078);--color-cyan-400:oklch(.789 .154 211.53);--color-cyan-500:oklch(.715 .143 215.221);--color-cyan-600:oklch(.609 .126 221.723);--color-cyan-700:oklch(.52 .105 223.128);--color-cyan-800:oklch(.45 .085 224.283);--color-cyan-900:oklch(.398 .07 227.392);--color-cyan-950:oklch(.302 .056 229.695);--color-sky-50:oklch(.977 .013 236.62);--color-sky-100:oklch(.951 .026 236.824);--color-sky-200:oklch(.901 .058 230.902);--color-sky-300:oklch(.828 .111 230.318);--color-sky-400:oklch(.746 .16 232.661);--color-sky-500:oklch(.685 .169 237.323);--color-sky-600:oklch(.588 .158 241.966);--color-sky-700:oklch(.5 .134 242.749);--color-sky-800:oklch(.443 .11 240.79);--color-sky-900:oklch(.391 .09 240.876);--color-sky-950:oklch(.293 .066 243.157);--color-blue-50:oklch(.97 .014 254.604);--color-blue-100:oklch(.932 .032 255.585);--color-blue-200:oklch(.882 .059 254.128);--color-blue-300:oklch(.809 .105 251.813);--color-blue-400:oklch(.707 .165 254.624);--color-blue-500:oklch(.623 .214 259.815);--color-blue-600:oklch(.546 .245 262.881);--color-blue-700:oklch(.488 .243 264.376);--color-blue-800:oklch(.424 .199 265.638);--color-blue-900:oklch(.379 .146 265.522);--color-blue-950:oklch(.282 .091 267.935);--color-indigo-50:oklch(.962 .018 272.314);--color-indigo-100:oklch(.93 .034 272.788);--color-indigo-200:oklch(.87 .065 274.039);--color-indigo-300:oklch(.785 .115 274.713);--color-indigo-400:oklch(.673 .182 276.935);--color-indigo-500:oklch(.585 .233 277.117);--color-indigo-600:oklch(.511 .262 276.966);--color-indigo-700:oklch(.457 .24 277.023);--color-indigo-800:oklch(.398 .195 277.366);--color-indigo-900:oklch(.359 .144 278.697);--color-indigo-950:oklch(.257 .09 281.288);--color-violet-50:oklch(.969 .016 293.756);--color-violet-100:oklch(.943 .029 294.588);--color-violet-200:oklch(.894 .057 293.283);--color-violet-300:oklch(.811 .111 293.571);--color-violet-400:oklch(.702 .183 293.541);--color-violet-500:oklch(.606 .25 292.717);--color-violet-600:oklch(.541 .281 293.009);--color-violet-700:oklch(.491 .27 292.581);--color-violet-800:oklch(.432 .232 292.759);--color-violet-900:oklch(.38 .189 293.745);--color-violet-950:oklch(.283 .141 291.089);--color-purple-50:oklch(.977 .014 308.299);--color-purple-100:oklch(.946 .033 307.174);--color-purple-200:oklch(.902 .063 306.703);--color-purple-300:oklch(.827 .119 306.383);--color-purple-400:oklch(.714 .203 305.504);--color-purple-500:oklch(.627 .265 303.9);--color-purple-600:oklch(.558 .288 302.321);--color-purple-700:oklch(.496 .265 301.924);--color-purple-800:oklch(.438 .218 303.724);--color-purple-900:oklch(.381 .176 304.987);--color-purple-950:oklch(.291 .149 302.717);--color-fuchsia-50:oklch(.977 .017 320.058);--color-fuchsia-100:oklch(.952 .037 318.852);--color-fuchsia-200:oklch(.903 .076 319.62);--color-fuchsia-300:oklch(.833 .145 321.434);--color-fuchsia-400:oklch(.74 .238 322.16);--color-fuchsia-500:oklch(.667 .295 322.15);--color-fuchsia-600:oklch(.591 .293 322.896);--color-fuchsia-700:oklch(.518 .253 323.949);--color-fuchsia-800:oklch(.452 .211 324.591);--color-fuchsia-900:oklch(.401 .17 325.612);--color-fuchsia-950:oklch(.293 .136 325.661);--color-pink-50:oklch(.971 .014 343.198);--color-pink-100:oklch(.948 .028 342.258);--color-pink-200:oklch(.899 .061 343.231);--color-pink-300:oklch(.823 .12 346.018);--color-pink-400:oklch(.718 .202 349.761);--color-pink-500:oklch(.656 .241 354.308);--color-pink-600:oklch(.592 .249 .584);--color-pink-700:oklch(.525 .223 3.958);--color-pink-800:oklch(.459 .187 3.815);--color-pink-900:oklch(.408 .153 2.432);--color-pink-950:oklch(.284 .109 3.907);--color-rose-50:oklch(.969 .015 12.422);--color-rose-100:oklch(.941 .03 12.58);--color-rose-200:oklch(.892 .058 10.001);--color-rose-300:oklch(.81 .117 11.638);--color-rose-400:oklch(.712 .194 13.428);--color-rose-500:oklch(.645 .246 16.439);--color-rose-600:oklch(.586 .253 17.585);--color-rose-700:oklch(.514 .222 16.935);--color-rose-800:oklch(.455 .188 13.697);--color-rose-900:oklch(.41 .159 10.272);--color-rose-950:oklch(.271 .105 12.094);--color-slate-50:oklch(.984 .003 247.858);--color-slate-100:oklch(.968 .007 247.896);--color-slate-200:oklch(.929 .013 255.508);--color-slate-300:oklch(.869 .022 252.894);--color-slate-400:oklch(.704 .04 256.788);--color-slate-500:oklch(.554 .046 257.417);--color-slate-600:oklch(.446 .043 257.281);--color-slate-700:oklch(.372 .044 257.287);--color-slate-800:oklch(.279 .041 260.031);--color-slate-900:oklch(.208 .042 265.755);--color-slate-950:oklch(.129 .042 264.695);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-400:oklch(.707 .022 261.325);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-800:oklch(.278 .033 256.848);--color-gray-900:oklch(.21 .034 264.665);--color-gray-950:oklch(.13 .028 261.692);--color-zinc-50:oklch(.985 0 0);--color-zinc-100:oklch(.967 .001 286.375);--color-zinc-200:oklch(.92 .004 286.32);--color-zinc-300:oklch(.871 .006 286.286);--color-zinc-400:oklch(.705 .015 286.067);--color-zinc-500:oklch(.552 .016 285.938);--color-zinc-600:oklch(.442 .017 285.786);--color-zinc-700:oklch(.37 .013 285.805);--color-zinc-800:oklch(.274 .006 286.033);--color-zinc-900:oklch(.21 .006 285.885);--color-zinc-950:oklch(.141 .005 285.823);--color-neutral-50:oklch(.985 0 0);--color-neutral-100:oklch(.97 0 0);--color-neutral-200:oklch(.922 0 0);--color-neutral-300:oklch(.87 0 0);--color-neutral-400:oklch(.708 0 0);--color-neutral-500:oklch(.556 0 0);--color-neutral-600:oklch(.439 0 0);--color-neutral-700:oklch(.371 0 0);--color-neutral-800:oklch(.269 0 0);--color-neutral-900:oklch(.205 0 0);--color-neutral-950:oklch(.145 0 0);--color-stone-50:oklch(.985 .001 106.423);--color-stone-100:oklch(.97 .001 106.424);--color-stone-200:oklch(.923 .003 48.717);--color-stone-300:oklch(.869 .005 56.366);--color-stone-400:oklch(.709 .01 56.259);--color-stone-500:oklch(.553 .013 58.071);--color-stone-600:oklch(.444 .011 73.639);--color-stone-700:oklch(.374 .01 67.558);--color-stone-800:oklch(.268 .007 34.298);--color-stone-900:oklch(.216 .006 56.043);--color-stone-950:oklch(.147 .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:after,:before,::backdrop{border-color:var(--color-gray-200,currentColor)}::file-selector-button{border-color:var(--color-gray-200,currentColor)}}@layer components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-2\/4{top:50%}.left-2\/4{left:50%}.col-span-1{grid-column:span 1/span 1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0{margin-inline:calc(var(--spacing)*0)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.block{display:block}.contents{display:contents}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing)*8)}.w-full{width:100%}.w-max{width:max-content}.border-collapse{border-collapse:collapse}.-translate-x-2\/4{--tw-translate-x:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-2\/4{--tw-translate-y:calc(calc(2/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-600{border-color:var(--color-zinc-600)}.bg-\[\#296236\]{background-color:#296236}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.underline{text-decoration-line:underline}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black{--tw-shadow-color:var(--color-black)}.ring-zinc-500{--tw-ring-color:var(--color-zinc-500)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.outline-hidden{outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[hostname\:port\]{hostname:port}@media (hover:hover){.hover\:bg-\[\#23552f\]:hover{background-color:#23552f}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-75:disabled{opacity:.75}@media (width>=48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-4{grid-column:span 4/span 4}.md\:block{display:block}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:px-20{padding-inline:calc(var(--spacing)*20)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (prefers-color-scheme:dark){.dark\:border-zinc-300{border-color:var(--color-zinc-300)}.dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark\:text-white{color:var(--color-white)}.dark\:shadow-white{--tw-shadow-color:var(--color-white)}.dark\:ring-zinc-200{--tw-ring-color:var(--color-zinc-200)}}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"*";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"*";inherits:false;initial-value:rotateZ(0)}@property --tw-skew-x{syntax:"*";inherits:false;initial-value:skewX(0)}@property --tw-skew-y{syntax:"*";inherits:false;initial-value:skewY(0)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file From 89504d95b4497d6c262f6b64c84805772e976dd6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:20:54 +0000 Subject: [PATCH 137/137] chore(deps): update zoeyvid/nginx-quic docker tag to v421 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7bb3d4899..8b5a33a74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk upgrade --no-cache -a && \ sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf -FROM zoeyvid/nginx-quic:420-python +FROM zoeyvid/nginx-quic:421-python SHELL ["/bin/ash", "-eo", "pipefail", "-c"] COPY rootfs / COPY src /app/src