├── .github ├── dependabot.yml └── workflows │ └── run-tests.yml ├── .gitignore ├── .prettierrc.yml ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── docs └── images │ ├── console-panel.png │ ├── error-alert.png │ ├── error-hint.png │ ├── error-multi.png │ ├── error-single-2-columns.png │ ├── error-single-4-columns.png │ ├── json-resource.png │ ├── json.png │ ├── panel.png │ └── slack.png ├── mix-manifest.json ├── package.json ├── phpunit.xml ├── src ├── Checkers │ ├── Artisan.php │ ├── Base.php │ ├── Broadcasting.php │ ├── Cache.php │ ├── Certificate.php │ ├── CloudStorage.php │ ├── Composer.php │ ├── Contract.php │ ├── Database.php │ ├── DirectoryAndFilePresence.php │ ├── DiskSpace.php │ ├── Docusign.php │ ├── Expression.php │ ├── Extensions.php │ ├── Filesystem.php │ ├── Framework.php │ ├── Health.php │ ├── HealthPanel.php │ ├── Horizon.php │ ├── Http.php │ ├── Https.php │ ├── Mail.php │ ├── MixManifest.php │ ├── NotInDebugMode.php │ ├── Ping.php │ ├── PortCheck.php │ ├── Process.php │ ├── Queue.php │ ├── Redis.php │ ├── SecurityChecker.php │ ├── ServerLoad.php │ ├── ServerUptime.php │ ├── ServerVars.php │ └── Writable.php ├── Commands.php ├── Console │ └── Commands │ │ ├── HealthCheckCommand.php │ │ └── HealthPanelCommand.php ├── Data │ └── Models │ │ └── HealthCheck.php ├── Events │ ├── HealthPing.php │ └── RaiseHealthIssue.php ├── Http │ ├── Controllers │ │ ├── Broadcasting.php │ │ └── Health.php │ └── Middleware │ │ └── LocallyProtected.php ├── Listeners │ └── NotifyHealthIssue.php ├── Notifications │ └── HealthStatus.php ├── Service.php ├── ServiceProvider.php ├── Support │ ├── Broadcasting │ │ ├── pusher.blade.php │ │ └── server.js │ ├── Cache.php │ ├── Constants.php │ ├── Jobs │ │ └── TestJob.php │ ├── LocallyProtected.php │ ├── Resource.php │ ├── ResourceChecker.php │ ├── ResourceLoader.php │ ├── Result.php │ ├── Target.php │ ├── Timer.php │ ├── Traits │ │ ├── Database.php │ │ ├── HandleExceptions.php │ │ ├── ImportProperties.php │ │ ├── Routing.php │ │ └── ToArray.php │ └── helpers.php ├── config │ ├── .gitkeep │ ├── deprecated │ │ └── Health.yml │ ├── health.php │ └── resources │ │ ├── API.yml │ │ ├── Adyen.yml │ │ ├── AppKey.yml │ │ ├── Broadcasting.yml │ │ ├── Cache.yml │ │ ├── Certificate.yml │ │ ├── CheckoutCom.yml │ │ ├── ConfigurationCached.yml │ │ ├── Database.yml │ │ ├── DebugMode.yml │ │ ├── DirectoryPermissions.yml │ │ ├── DiskSpace.yml │ │ ├── DocuSign.yml │ │ ├── Dynamics.yml │ │ ├── ElasticsearchConnectable.yml │ │ ├── EnvExists.yml │ │ ├── Extensions.yml │ │ ├── Filesystem.yml │ │ ├── Framework.yml │ │ ├── HealthPanel.yml │ │ ├── Horizon.yml │ │ ├── Http.yml │ │ ├── Https.yml │ │ ├── LaravelServices.yml │ │ ├── Latency.yml │ │ ├── LocalStorage.yml │ │ ├── Mail.yml │ │ ├── MailgunConnectable.yml │ │ ├── MemcachedConnectable.yml │ │ ├── MigrationsUpToDate.yml │ │ ├── MixManifest.yml │ │ ├── MySql.yml │ │ ├── MySqlConnectable.yml │ │ ├── NewrelicDeamon.yml │ │ ├── NginxServer.yml │ │ ├── PackagesUpToDate.yml │ │ ├── Php.yml │ │ ├── PostgreSqlConnectable.yml │ │ ├── PostgreSqlServer.yml │ │ ├── Queue.yml │ │ ├── QueueWorkers.yml │ │ ├── RebootRequired.yml │ │ ├── Redis.yml │ │ ├── RedisConnectable.yml │ │ ├── RedisServer.yml │ │ ├── RoutesCached.yml │ │ ├── S3.yml │ │ ├── SecurityChecker.yml │ │ ├── SeeTickets.yml │ │ ├── Sendinblue.yml │ │ ├── ServerLoad.yml │ │ ├── ServerUptime.yml │ │ ├── ServerVars.yml │ │ ├── Sshd.yml │ │ └── Supervisor.yml ├── database │ └── migrations │ │ └── 2018_09_09_000001_create_table_health_checks.php └── resources │ ├── dist │ ├── css │ │ └── app.css │ └── js │ │ ├── app.js │ │ └── app.js.LICENSE.txt │ ├── js │ ├── app.js │ ├── bootstrap.js │ ├── components │ │ ├── Chart.vue │ │ ├── Panel.vue │ │ └── Target.vue │ └── mixins │ │ └── routes.js │ ├── sass │ ├── _variables.scss │ └── app.scss │ └── views │ ├── default │ ├── email.blade.php │ ├── empty-panel.blade.php │ ├── html.blade.php │ ├── panel.blade.php │ └── partials │ │ ├── style.blade.php │ │ └── well.blade.php │ └── html.blade.php ├── tests └── PhpUnit │ ├── Service │ ├── ServiceTest.php │ └── TimerTest.php │ ├── TestCase.php │ └── bootstrap.php ├── webpack.mix.js └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | 8 | jobs: 9 | php-tests: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | php: [8.1, 8.0, 7.4, 7.3] 15 | laravel: [9.*, 8.*] 16 | include: 17 | - laravel: 9.* 18 | testbench: 7.* 19 | - laravel: 8.* 20 | testbench: 6.* 21 | 22 | name: P${{ matrix.php }} - L${{ matrix.laravel }} 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Install dependencies 29 | run: | 30 | git config --global user.email "acr@antoniocarlosribeiro.com"; git config --global user.name "Antonio Ribeiro" 31 | 32 | - name: Install dependencies 33 | run: | 34 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --dev --no-interaction --no-suggest 35 | 36 | - name: Execute tests 37 | run: vendor/bin/phpunit 38 | 39 | # after_script: 40 | # - | 41 | # if [[ "$TRAVIS_PHP_VERSION" != 'hhvm' && "$TRAVIS_PHP_VERSION" != '7.0' ]]; then 42 | # wget https://scrutinizer-ci.com/ocular.phar 43 | # php ocular.phar code-coverage:upload --format=php-clover coverage.clover 44 | # fi 45 | # --coverage-clover=coverage.clover 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | .DS_Store 4 | tests/_output/* 5 | coverage 6 | node_modules/ 7 | .phpunit.result.cache 8 | composer.lock 9 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | phpVersion: "7.4" 2 | trailingComma: all 3 | printWidth: 80 4 | tabWidth: 4 5 | useTabs: false 6 | singleQuote: true 7 | trailingCommaPHP: false 8 | braceStyle: psr-2 9 | requirePragma: false 10 | insertPragma: false 11 | semi: false 12 | vueIndentScriptAndStyle: true 13 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | environment: 3 | php: 4 | version: 7.2 5 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: php 3 | 4 | php: 5 | - 7.1 6 | - 7.2 7 | - 7.3 8 | - 7.4 9 | - nightly 10 | 11 | sudo: false 12 | 13 | cache: 14 | directories: 15 | - $HOME/.composer/cache 16 | 17 | install: 18 | - | 19 | if [[ "$TRAVIS_PHP_VERSION" == 'nightly' ]]; then 20 | travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist --ignore-platform-reqs 21 | else 22 | travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist 23 | fi 24 | 25 | script: 26 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 27 | 28 | after_script: 29 | - | 30 | if [[ "$TRAVIS_PHP_VERSION" != 'nightly' ]]; then 31 | wget https://scrutinizer-ci.com/ocular.phar 32 | php ocular.phar code-coverage:upload --format=php-clover coverage.clover 33 | fi 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.5 - 2022-11-25 4 | - Improve failing messages for the HealthPanel checker 5 | 6 | ## 2.0.4 - 2022-11-24 7 | - Fix Laravel Cache::remember() to use seconds instead 8 | 9 | ## 2.0.3 - 2022-11-23 10 | - Allow disabling notifications by the caller (web,console) 11 | 12 | ## 2.0.2 - 2022-11-14 13 | - Http/Https checker: allow setting the URL as a separate object to fix a YAML limitation 14 | 15 | ## 2.0.1 - 2022-11-14 16 | - Queue checker: allow defining the queue name on config 17 | 18 | ## 2.0.0 - 2022-11-14 19 | - Bump Laravel version minimum to 8.0 and PHP to 7.3 20 | 21 | ## 1.1.6 - 2022-11-14 22 | - Fix not all parameters being sent to Guzzle for the HealthPanel checker 23 | 24 | ## 1.1.5 - 2022-11-14 25 | - Allow checking if the API response is a valid JSON 26 | 27 | ## 1.1.4 - 2022-11-07 28 | - Createad a HealthPanel checker to allow people to create a panels of remote panels 29 | - Added a "link" property that will open a new page when clicked 30 | 31 | ## 1.1.2 - 2021-11-07 32 | - Add Server Vars ($_SERVER) checker, to help developers check if configuration is good and CDN requests to the app have the required data. 33 | 34 | ## 1.1.1 - 2021-11-25 35 | - Add PHP extensions checker 36 | 37 | ## 1.1.0 - 2021-10-24 38 | - Add Support for PHP 8.1 39 | - Add Checkout.com checker 40 | - Refactor Securit Checker to use a new tool 41 | - Add total time to console panel 42 | 43 | ## 1.0.1 - 2020-10-15 44 | - Check certificates using openSSL 45 | 46 | ## 1.0.0 - 2020-10-10 47 | - Add SSL certificates checker 48 | - Allow using URL parameter at the same level of method 49 | - Add support for API checks via the HTTP checker and add example 50 | - Add URL to error message when HTTP is not able to connect 51 | - Allow adding HTTP headers to requests 52 | - Allow HTTP checker method to be changed 53 | - Allow HTTP checker to POST with data 54 | - Allow configuration of Guzzle on the HTTP checker 55 | 56 | ## 0.11.1 - 2020-10-01 57 | ## 0.11.0 - 2020-10-01 58 | - Add Laravel 8 support 59 | 60 | ## 0.10.4 - 2020-10-01 61 | - Fixed the deprecated use of implode 62 | - Add support for phpunit/php-timer 3 63 | - Added $dateFormat for MS SQL Server support 64 | - Fix SensioLabsSecurityChecker results 65 | - Fix Laravel deprecated helpers 66 | - Fix notifier 67 | 68 | ## 0.10.2 - 2020-08-01 69 | - Fix Laravel 7.22.0 breaking change 70 | 71 | ## 0.10.0 - 2019-09-11 72 | ### Added 73 | - Laravel 6 support 74 | - Compatibility with PHP 7.4 75 | - Email notification improvements 76 | - Lumen support 77 | 78 | ## 0.9.16 - 2018-10-08 79 | ### Fixed 80 | - Properly use routes from configuration on axios requests. 81 | Please check if your route names all starts with 'pragmarx.health.', it's mandatory now 82 | 83 | ## 0.9.15 - 2018-09-27 84 | ### Changed 85 | - Composer outdated now ignores major versions 86 | ### Fixed 87 | - Buttons alignment 88 | 89 | ## 0.9.14 - 2018-09-25 90 | ### Added 91 | - Laravel Lumen support 92 | 93 | ## 0.9.13 - 2018-09-24 94 | ### Fixed 95 | - Fix Result status in Security Checker 96 | 97 | ## 0.9.12 - 2018-09-24 98 | ### Fixed 99 | - Fix content types for javascript import 100 | 101 | ## 0.9.11 - 2018-09-23 102 | ### Changed 103 | - Notifications are disabled by default 104 | ### Fixed 105 | - Uptime checker 106 | - Use full namespaces for Laravel façades 107 | 108 | ## 0.9.10 - 2018-09-22 109 | ### Added 110 | - Input to filter resources 111 | ### Changed 112 | - Flush cache when clicking to refresh one resource in the panel 113 | - Binaries (composer, ping) are now configurable 114 | - Added config key 'services' to configure services binaries 115 | - Checker Ping updated, please review configuration for Latency resources 116 | - Checker Composer updated, please review configuration for PackagesUpToDate resources 117 | ### Fix 118 | - Laravel 5.5 support 119 | 120 | ## 0.9.9 - 2018-09-19 121 | ## 0.9.8 - 2018-09-19 122 | ## 0.9.7 - 2018-09-17 123 | ## 0.9.6 - 2018-09-14 124 | ### Fixed 125 | - Some minor bugs 126 | 127 | ## 0.9.5 - 2018-09-12 128 | ### Changed 129 | - Complete refactor 130 | - Can now create many targets inside resources 131 | - Resource charts 132 | - VueJS Panel 133 | - Allow users to click to refresh one item 134 | - Store data on database to plot charts 135 | - Lots of new Checkers and resources 136 | 137 | ## 0.4.0 - 2017-12-18 138 | ### Changed 139 | - User will need change config files and surround expressions ({{ }}) with "" (breaking change) 140 | - Upgraded to Laravel 5.5 & PHP 7.0+ 141 | - Added support for PHP 7.2 142 | 143 | ## 0.3.3 - 2017-04-20 144 | ### Fixed 145 | - Docusign checker 146 | 147 | ## 0.3.2 - 2017-04-16 148 | ### Fixed 149 | - Yaml functions parser when using strings as parameters 150 | 151 | ## 0.3.0 - 2017-02-25 152 | ### Changed 153 | - Now we can use Yaml files to configure resources 154 | 155 | ## 0.1.8 - 2017-01-09 156 | ### Added 157 | - Broadcasting checker for Redis and Pusher 158 | 159 | ## 0.1.7 - 2017-01-06 160 | ### Added 161 | - Server Uptime checker 162 | - Server Load checker 163 | 164 | ## 0.1.6 - 2017-01-05 165 | ### Added 166 | - Queue checker 167 | - Redis checker 168 | 169 | ## 0.1.0 - 2016-11-28 170 | ### Added 171 | - First usable version 172 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Antonio Carlos Ribeiro 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pragmarx/health", 3 | "description": "Laravel Server & App Health Monitor and Notifier", 4 | "keywords": [ 5 | "health", 6 | "laravel", 7 | "pragmarx", 8 | "notifications", 9 | "panel", 10 | "monitor", 11 | "server", 12 | "app" 13 | ], 14 | "license": "BSD-3-Clause", 15 | "authors": [ 16 | { 17 | "name": "Antonio Carlos Ribeiro", 18 | "email": "acr@antoniocarlosribeiro.com", 19 | "role": "Creator & Designer" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=7.3", 24 | "illuminate/support": ">=8.0", 25 | "pragmarx/yaml": ">=0.1", 26 | "phpunit/php-timer": "^1.0|^2.0|^3.0|^4.0|^5.0|^6.0", 27 | "ext-json": "*" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": ">=6.5", 31 | "laravel/laravel": ">=8.0", 32 | "orchestra/testbench": "5.*|6.*|7.*|8.*|9.*", 33 | "guzzlehttp/guzzle": ">=6.0", 34 | "docusign/esign-client": ">=2.0", 35 | "predis/predis": ">=1.0", 36 | "nesbot/carbon": ">=1.34", 37 | "laravel/framework": "9.*|10.*|11.*" 38 | }, 39 | "suggest": { 40 | "guzzlehttp/guzzle": ">=6.0", 41 | "docusign/esign-client": ">=2.0", 42 | "predis/predis": ">=1.0", 43 | "league/flysystem-aws-s3-v3": ">=1.0", 44 | "sensiolabs/security-checker": ">=4.1", 45 | "spatie/ssl-certificate": ">=1.0" 46 | }, 47 | "autoload": { 48 | "files": [ 49 | "src/Support/helpers.php" 50 | ], 51 | "psr-4": { 52 | "PragmaRX\\Health\\": "src/" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "PragmaRX\\Health\\Tests\\PhpUnit\\": "tests/PhpUnit/" 58 | } 59 | }, 60 | "extra": { 61 | "laravel": { 62 | "providers": [ 63 | "PragmaRX\\Health\\ServiceProvider" 64 | ] 65 | } 66 | }, 67 | "scripts": { 68 | "test": [ 69 | "@composer install", 70 | "vendor/bin/phpunit" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /docs/images/console-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/console-panel.png -------------------------------------------------------------------------------- /docs/images/error-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/error-alert.png -------------------------------------------------------------------------------- /docs/images/error-hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/error-hint.png -------------------------------------------------------------------------------- /docs/images/error-multi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/error-multi.png -------------------------------------------------------------------------------- /docs/images/error-single-2-columns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/error-single-2-columns.png -------------------------------------------------------------------------------- /docs/images/error-single-4-columns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/error-single-4-columns.png -------------------------------------------------------------------------------- /docs/images/json-resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/json-resource.png -------------------------------------------------------------------------------- /docs/images/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/json.png -------------------------------------------------------------------------------- /docs/images/panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/panel.png -------------------------------------------------------------------------------- /docs/images/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonioribeiro/health/f500876d69100f91049cfbcc2159a5ddb6f7c306/docs/images/slack.png -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/src/resources/dist/js/app.js": "/src/resources/dist/js/app.js", 3 | "/src/resources/dist/css/app.css": "/src/resources/dist/css/app.css" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "npm run development -- --watch", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.21.0", 14 | "bootstrap": "^4.5.3", 15 | "chart.js": "^2.9.4", 16 | "cross-env": "^7.0.2", 17 | "jquery": "^3.2", 18 | "laravel-mix": "^5.0.7", 19 | "lodash": "^4.17.20", 20 | "popper.js": "^1.12", 21 | "resolve-url-loader": "^3.1.2", 22 | "sass": "^1.27.0", 23 | "sass-loader": "^10.0.4", 24 | "sweetalert2": "^10.8.1", 25 | "vue": "^2.5.7", 26 | "vue-chartjs": "^3.5.1", 27 | "vue-template-compiler": "^2.6.12" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ./tests/PhpUnit 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Checkers/Artisan.php: -------------------------------------------------------------------------------- 1 | executeAndCheck() 17 | ? $this->makeHealthyResult() 18 | : $this->makeResult(false, $this->target->getErrorMessage()); 19 | } 20 | 21 | /** 22 | * @return bool 23 | */ 24 | protected function executeAndCheck() 25 | { 26 | $this->executeArtisan(); 27 | 28 | return $this->checkArtisanOutput(); 29 | } 30 | 31 | /** 32 | * @return bool 33 | */ 34 | protected function checkArtisanOutput() 35 | { 36 | $output = IlluminateArtisan::output(); 37 | 38 | return 39 | $output && preg_match("|{$this->target->shouldReturn}|", $output); 40 | } 41 | 42 | protected function executeArtisan() 43 | { 44 | IlluminateArtisan::call( 45 | $this->target->command['name'], 46 | $this->target->command['options']->toArray() 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Checkers/Base.php: -------------------------------------------------------------------------------- 1 | check(); 27 | 28 | $result->elapsedTime = Timer::stop(); 29 | 30 | return $result; 31 | } 32 | 33 | /** 34 | * Create base directory for files. 35 | * 36 | * @param $fileName 37 | */ 38 | protected function makeDir($fileName) 39 | { 40 | $dir = dirname($fileName); 41 | 42 | if (! file_exists($dir)) { 43 | mkdir($dir, 0775, true); 44 | } 45 | } 46 | 47 | /** 48 | * Make a result. 49 | * 50 | * @param bool $healthy 51 | * @param null $errorMessage 52 | * @return Result 53 | */ 54 | public function makeResult($healthy = true, $errorMessage = null) 55 | { 56 | return new Result($healthy, $errorMessage); 57 | } 58 | 59 | /** 60 | * Make a healthy result. 61 | * 62 | * @return Result 63 | */ 64 | protected function makeHealthyResult() 65 | { 66 | return $this->makeResult(); 67 | } 68 | 69 | /** 70 | * Make a result from an exception. 71 | * 72 | * @param $exception 73 | * @return Result 74 | */ 75 | protected function makeResultFromException($exception) 76 | { 77 | return $this->makeResult(false, $exception->getMessage()); 78 | } 79 | 80 | /** 81 | * @param $resources 82 | * @return mixed 83 | */ 84 | public function healthy($resources) 85 | { 86 | return $this->healthy; 87 | } 88 | 89 | /** 90 | * @param $resources 91 | * @return mixed 92 | */ 93 | public function message($resources) 94 | { 95 | return $this->message; 96 | } 97 | 98 | /** 99 | * Save result to database. 100 | * 101 | * @param $result 102 | * @return 103 | */ 104 | protected function saveToDatabase($result) 105 | { 106 | if ($this->databaseEnabled()) { 107 | return $this->saveResultsToDatabase($this->target, $result); 108 | } 109 | } 110 | 111 | /** 112 | * @param $healthy 113 | */ 114 | public function setHealthy($healthy) 115 | { 116 | $this->healthy = $healthy; 117 | } 118 | 119 | /** 120 | * @param $message 121 | */ 122 | public function setMessage($message) 123 | { 124 | $this->message = $message; 125 | } 126 | 127 | /** 128 | * @return array 129 | */ 130 | public function healthArray() 131 | { 132 | return [ 133 | 'healthy' => $this->healthy, 134 | 135 | 'message' => $this->message, 136 | ]; 137 | } 138 | 139 | /** 140 | * Load cache. 141 | * 142 | * @return \Illuminate\Support\Collection 143 | */ 144 | public function load() 145 | { 146 | if (! file_exists($file = $this->getFileName())) { 147 | return collect(); 148 | } 149 | 150 | return collect(json_decode(file_get_contents($file), true)); 151 | } 152 | 153 | /** 154 | * Persist to database cache file. 155 | * 156 | * @param $data 157 | */ 158 | public function persist($data = null) 159 | { 160 | if (is_null($data)) { 161 | $data = $this->database->toArray(); 162 | } 163 | 164 | if (! is_array($data)) { 165 | $data = $data->toArray(); 166 | } 167 | 168 | $this->makeDir($this->getFileName()); 169 | 170 | file_put_contents($this->getFileName(), json_encode($data)); 171 | } 172 | 173 | /** 174 | * Get cache filename. 175 | * 176 | * @return string 177 | */ 178 | protected function getFileName() 179 | { 180 | return $this->target->saveTo ?? ''; 181 | } 182 | 183 | /** 184 | * Target setter. 185 | * 186 | * @param $target 187 | * @return $this 188 | */ 189 | public function setTarget($target) 190 | { 191 | $this->target = $target; 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * Check the target. 198 | * 199 | * @param $target 200 | * @return \PragmaRX\Health\Support\Result 201 | */ 202 | public function checkTarget() 203 | { 204 | $result = $this->checkAndStoreTime(); 205 | 206 | $result->checks = $this->saveToDatabase($result); 207 | 208 | return $result; 209 | } 210 | 211 | /** 212 | * Get the total elapsed time for this resource. 213 | * 214 | * @param $target 215 | * @return string 216 | */ 217 | public function getTotalTime() 218 | { 219 | return rtrim(sprintf('%.6f', $this->target->result->elapsedTime), '0'); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Checkers/Broadcasting.php: -------------------------------------------------------------------------------- 1 | target->routes->each(function ($route, $name) { 18 | $this->registerRoute($route, $name); 19 | }); 20 | } 21 | 22 | /** 23 | * Check resource. 24 | * 25 | * @return Result 26 | */ 27 | public function check() 28 | { 29 | $this->loadDatabase(); 30 | 31 | $this->bootRouter(); 32 | 33 | $isHealthy = ! $this->pingTimedout(); 34 | 35 | $this->createPing(); 36 | 37 | $this->dispatchEvent(); 38 | 39 | return $this->makeResult($isHealthy, $this->target->getErrorMessage()); 40 | } 41 | 42 | /** 43 | * Dispatch event. 44 | */ 45 | protected function dispatchEvent() 46 | { 47 | event( 48 | new HealthPing( 49 | $this->target->channel, 50 | route($this->target->routeName, [$this->target->secret]), 51 | $this->target 52 | ) 53 | ); 54 | } 55 | 56 | /** 57 | * Create and persist ping. 58 | */ 59 | protected function createPing() 60 | { 61 | $this->database->push($this->createPingRow()); 62 | 63 | $this->persist(); 64 | } 65 | 66 | /** 67 | * Create ping row array. 68 | * 69 | * @return array 70 | */ 71 | protected function createPingRow() 72 | { 73 | return [ 74 | 'pinged_at' => Carbon::now(), 75 | 'ponged_at' => null, 76 | 'secret' => $this->target->secret, 77 | ]; 78 | } 79 | 80 | /** 81 | * Parse date. 82 | * 83 | * @param $date 84 | * @return Carbon 85 | */ 86 | protected function parseDate($date) 87 | { 88 | return Carbon::parse($date['date'], $date['timezone']); 89 | } 90 | 91 | /** 92 | * Create and persist pong. 93 | * 94 | * @param $secret 95 | */ 96 | public function pong($secret) 97 | { 98 | $this->database = $this->database->map(function ($item) use ($secret) { 99 | if ($item['secret'] == $secret) { 100 | $item['ponged_at'] = Carbon::now(); 101 | } 102 | 103 | return $item; 104 | }); 105 | 106 | $this->persist(); 107 | } 108 | 109 | /** 110 | * Check if a ping timed out. 111 | * 112 | * @return bool 113 | */ 114 | protected function pingTimedout() 115 | { 116 | $timedout = false; 117 | 118 | $this->database = $this->database->filter(function ($item) use ( 119 | &$timedout 120 | ) { 121 | if (! $item['ponged_at']) { 122 | if ( 123 | Carbon::now()->diffInSeconds( 124 | $this->parseDate($item['pinged_at']) 125 | ) > $this->target->timeout 126 | ) { 127 | $timedout = true; 128 | 129 | return false; 130 | } 131 | 132 | return true; 133 | } 134 | 135 | return false; 136 | }); 137 | 138 | return $timedout; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Checkers/Cache.php: -------------------------------------------------------------------------------- 1 | getChecker(); 17 | 18 | $value1 = $this->getCached(); 19 | 20 | $value2 = $this->getCached(); 21 | 22 | if ($value1 !== $value2 || $value2 !== $checker()) { 23 | return $this->makeResult( 24 | false, 25 | $this->target->getErrorMessage() 26 | ); 27 | } 28 | 29 | return $this->makeHealthyResult(); 30 | } catch (\Exception $exception) { 31 | report($exception); 32 | 33 | return $this->makeResultFromException($exception); 34 | } 35 | } 36 | 37 | private function getCached() 38 | { 39 | $checker = $this->getChecker(); 40 | 41 | return IlluminateCache::remember( 42 | $this->target->key, 43 | $this->target->seconds, 44 | $checker 45 | ); 46 | } 47 | 48 | private function getChecker() 49 | { 50 | return function () { 51 | return 'DUMMY DATA'; 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Checkers/Certificate.php: -------------------------------------------------------------------------------- 1 | getResourceUrlArray(); 20 | 21 | $first = collect($resources)->first(); 22 | 23 | if (filled($first)) { 24 | $this->target->setDisplay("{$first}"); 25 | } 26 | 27 | try { 28 | foreach ($resources as $url) { 29 | [$healthy, $message] = $this->checkCertificate($url); 30 | 31 | if (! $healthy) { 32 | return $this->makeResult(false, $message); 33 | } 34 | } 35 | 36 | return $this->makeHealthyResult(); 37 | } catch (\Exception $exception) { 38 | report($exception); 39 | 40 | return $this->makeResultFromException($exception); 41 | } 42 | } 43 | 44 | /** 45 | * HTTP Checker. 46 | * 47 | * @return Result 48 | */ 49 | public function checkCertificate($url) 50 | { 51 | return $this->checkHostCertificate($this->getHost($url)); 52 | } 53 | 54 | /** 55 | * Get the error message. 56 | * 57 | * @return string 58 | */ 59 | protected function getErrorMessage($host) 60 | { 61 | return sprintf($this->target->resource->errorMessage, $host); 62 | } 63 | 64 | /** 65 | * Get the error message. 66 | * 67 | * @return string 68 | */ 69 | protected function getHost($url) 70 | { 71 | $parsed = parse_url($url); 72 | 73 | if (isset($parsed['host'])) { 74 | return $parsed['host']; 75 | } 76 | 77 | return $url; 78 | } 79 | 80 | /** 81 | * Get array of resource urls. 82 | * 83 | * @return array 84 | */ 85 | private function getResourceUrlArray() 86 | { 87 | if (is_a($this->target->urls, Collection::class)) { 88 | return $this->target->urls->toArray(); 89 | } 90 | 91 | return (array) $this->target->urls; 92 | } 93 | 94 | public function checkHostCertificate($host) 95 | { 96 | $result = collect([ 97 | 'openssl' => $this->checkCertificateWithOpenSSL($host), 98 | 99 | 'package' => [ 100 | SslCertificate::createForHostName($host)->isValid(), 101 | 'Invalid certificate', 102 | ], 103 | 104 | 'php' => $this->checkCertificateWithPhp($host), 105 | ]) 106 | ->filter(function ($result) { 107 | return $result[0] === false; 108 | }) 109 | ->first(); 110 | 111 | if ($result === null) { 112 | return [true, '']; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public function checkCertificateWithOpenSSL($host) 119 | { 120 | exec($this->makeCommand($host), $output); 121 | 122 | $result = collect($output) 123 | ->filter( 124 | function ($line) { 125 | return Str::contains( 126 | $line, 127 | $this->target->resource->verifyString 128 | ); 129 | } 130 | ) 131 | ->first(); 132 | 133 | if (blank($result)) { 134 | $output = blank($output) ? 'Unkown openssl error' : $output; 135 | 136 | return [false, json_encode($output)]; 137 | } 138 | 139 | return [ 140 | trim($result) == $this->target->resource->successString, 141 | $result, 142 | ]; 143 | } 144 | 145 | public function checkCertificateWithPhp($host) 146 | { 147 | try { 148 | $get = stream_context_create([ 149 | 'ssl' => ['capture_peer_cert' => true], 150 | ]); 151 | 152 | $read = stream_socket_client( 153 | 'ssl://'.$host.':443', 154 | $errno, 155 | $errstr, 156 | 30, 157 | STREAM_CLIENT_CONNECT, 158 | $get 159 | ); 160 | } catch (\Exception $exception) { 161 | return [false, $exception->getMessage()]; 162 | } 163 | 164 | return [true, '']; 165 | } 166 | 167 | /** 168 | * @param $host 169 | * @return string|string[] 170 | */ 171 | protected function makeCommand($host) 172 | { 173 | $command = $this->target->resource->command; 174 | 175 | $command = str_replace('{$options}', $this->target->options, $command); 176 | 177 | $command = str_replace('{$host}', $host, $command); 178 | 179 | return $command; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Checkers/CloudStorage.php: -------------------------------------------------------------------------------- 1 | target->driver)->put( 17 | $this->target->file, 18 | $this->target->contents 19 | ); 20 | 21 | $contents = Storage::disk($this->target->driver)->get( 22 | $this->target->file 23 | ); 24 | 25 | Storage::disk($this->target->driver)->delete($this->target->file); 26 | 27 | if ($contents !== $this->target->contents) { 28 | return $this->makeResult( 29 | false, 30 | $this->target->getErrorMessage() 31 | ); 32 | } 33 | 34 | return $this->makeHealthyResult(); 35 | } catch (\Exception $exception) { 36 | report($exception); 37 | 38 | return $this->makeResultFromException($exception); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Checkers/Composer.php: -------------------------------------------------------------------------------- 1 | executeCommand(); 18 | 19 | if ($outdated->count() > $this->target->resource->shouldCountAtMost) { 20 | return $this->makeResult( 21 | false, 22 | sprintf($this->target->getErrorMessage(), $outdated->count()) 23 | ); 24 | } 25 | 26 | return $this->makeHealthyResult(); 27 | } 28 | 29 | /** 30 | * Convert output to array. 31 | * 32 | * @param string $output 33 | * @return \Illuminate\Support\Collection|mixed 34 | */ 35 | protected function outputToCollection(string $output) 36 | { 37 | if ($this->target->resource->jsonResult) { 38 | return collect(json_decode($output, true) ?? collect([])); 39 | } 40 | 41 | return $output; 42 | } 43 | 44 | /** 45 | * Get the ping binary. 46 | */ 47 | protected function makeCommand() 48 | { 49 | return [ 50 | sprintf( 51 | '%s %s', 52 | $this->target->resource->binary, 53 | $this->target->resource->command 54 | ), 55 | ]; 56 | } 57 | 58 | /** 59 | * Execute the Composer command. 60 | * 61 | * @return \Illuminate\Support\Collection|mixed 62 | */ 63 | protected function executeCommand() 64 | { 65 | $process = new SymfonyProcess( 66 | $this->makeCommand(), 67 | $this->target->workingDir 68 | ); 69 | 70 | $process->run(); 71 | 72 | $output = $this->outputToCollection($process->getOutput()); 73 | 74 | if ($output->count() == 0) { 75 | return $output; 76 | } 77 | 78 | if ($rootItem = $this->target->resource->rootItem) { 79 | return collect($output[$rootItem]); 80 | } 81 | 82 | return $output; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Checkers/Contract.php: -------------------------------------------------------------------------------- 1 | target->type) { 18 | case 'find_first_model': 19 | return $this->findFirstModel(); 20 | case 'raw_query': 21 | return $this->rawQuery(); 22 | } 23 | 24 | throw new \Exception( 25 | "Target type '{$this->target->type}' does not exists" 26 | ); 27 | } catch (\Exception $exception) { 28 | report($exception); 29 | 30 | return $this->makeResultFromException($exception); 31 | } 32 | } 33 | 34 | protected function findFirstModel() 35 | { 36 | collect($this->target->models)->each(function ($model) { 37 | instantiate($model)->first(); 38 | }); 39 | 40 | return $this->makeHealthyResult(); 41 | } 42 | 43 | protected function getConnectionName() 44 | { 45 | return $this->target->connection == 'default' 46 | ? config('database.default') 47 | : $this->target->connection; 48 | } 49 | 50 | protected function rawQuery() 51 | { 52 | Timer::start(); 53 | 54 | DB::connection($this->getConnectionName())->select($this->target->query); 55 | 56 | $took = round(Timer::stop(), 5); 57 | $tookHuman = "{$took}s"; 58 | 59 | $this->target->setDisplay($this->target->name." ({$tookHuman})"); 60 | 61 | $result = 62 | $took > $this->target->maximumTime 63 | ? $this->makeResult( 64 | false, 65 | sprintf( 66 | $this->target->errorMessage, 67 | $took, 68 | $this->target->maximumTime 69 | ) 70 | ) 71 | : $this->makeHealthyResult(); 72 | 73 | $result->setValue($took)->setValueHuman($tookHuman); 74 | 75 | return $result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Checkers/DirectoryAndFilePresence.php: -------------------------------------------------------------------------------- 1 | checkPresence(); 37 | 38 | if ($result->count() == 0) { 39 | return $this->makeHealthyResult(); 40 | } 41 | 42 | return $this->makeResult( 43 | false, 44 | $this->target->getErrorMessage().' - '.implode(' ', $messages) 45 | ); 46 | } 47 | 48 | /** 49 | * Check file or dir presence. 50 | * 51 | * @return static 52 | */ 53 | protected function checkPresence() 54 | { 55 | $messages = []; 56 | 57 | $result = collect($this->getFiles()) 58 | ->map(function ($files, $type) use (&$messages) { 59 | $isGood = true; 60 | 61 | $files = collect($files); 62 | 63 | foreach ($files as $file) { 64 | if (! is_null($file)) { 65 | foreach ($this->getCheckers($type) as $checker) { 66 | if (is_string($message = $checker($file))) { 67 | $messages[] = $message; 68 | $isGood = false; 69 | } 70 | } 71 | } 72 | } 73 | 74 | return $isGood; 75 | }) 76 | ->filter(function ($value) { 77 | return $value === false; 78 | }); 79 | 80 | return [$messages, $result]; 81 | } 82 | 83 | public function getFiles() 84 | { 85 | return [ 86 | static::FILE_EXISTS => $this->target->fileExists, 87 | static::FILE_DOES_NOT_EXISTS => $this->target->fileDoNotExists, 88 | static::DIRECTORY_EXISTS => $this->target->directoryExists, 89 | static::DIRECTORY_DOES_NOT_EXISTS => $this->target->directoryDoNotExists, 90 | ]; 91 | } 92 | 93 | /** 94 | * Build file exists checker. 95 | * 96 | * @return \Closure 97 | */ 98 | public function buildFileExistsChecker() 99 | { 100 | return function ($file) { 101 | return $this->fileExists($file); 102 | }; 103 | } 104 | 105 | /** 106 | * Build file does not exists checker. 107 | * 108 | * @return \Closure 109 | */ 110 | public function buildFileDoesNotExistsChecker() 111 | { 112 | return function ($file) { 113 | return $this->fileDoesNotExists($file); 114 | }; 115 | } 116 | 117 | /** 118 | * Build is directory checker. 119 | * 120 | * @return \Closure 121 | */ 122 | public function buildIsDirectoryChecker() 123 | { 124 | return function ($file) { 125 | return $this->isDirectory($file); 126 | }; 127 | } 128 | 129 | /** 130 | * Get checkers. 131 | * 132 | * @return array 133 | */ 134 | public function getCheckers($checker) 135 | { 136 | switch ($checker) { 137 | case static::FILE_EXISTS: 138 | return [$this->buildFileExistsChecker()]; 139 | case static::FILE_DOES_NOT_EXISTS: 140 | return [$this->buildFileDoesNotExistsChecker()]; 141 | case static::DIRECTORY_EXISTS: 142 | return [ 143 | $this->buildFileExistsChecker(), 144 | $this->buildIsDirectoryChecker(), 145 | ]; 146 | case static::DIRECTORY_DOES_NOT_EXISTS: 147 | return [ 148 | $this->buildFileDoesNotExistsChecker(), 149 | $this->buildIsDirectoryChecker(), 150 | ]; 151 | } 152 | 153 | return []; 154 | } 155 | 156 | /** 157 | * Check if a file exists. 158 | * 159 | * @param $file 160 | * @return bool|string 161 | */ 162 | public function fileExists($file) 163 | { 164 | if (file_exists($file)) { 165 | return true; 166 | } 167 | 168 | return sprintf('File "%s" does not exists.', $file); 169 | } 170 | 171 | /** 172 | * Check if a file does not exists. 173 | * 174 | * @param $file 175 | * @return bool|string 176 | */ 177 | public function fileDoesNotExists($file) 178 | { 179 | if (! file_exists($file)) { 180 | return true; 181 | } 182 | 183 | return sprintf('File "%s" exists.', $file); 184 | } 185 | 186 | /** 187 | * Check if a path is a directory. 188 | * 189 | * @param $file 190 | * @return bool|string 191 | */ 192 | public function isDirectory($file) 193 | { 194 | if (is_dir($file)) { 195 | return true; 196 | } 197 | 198 | return sprintf('"%s" is not a directory.', $file); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/Checkers/DiskSpace.php: -------------------------------------------------------------------------------- 1 | target->path); 17 | 18 | if (! $this->isEnough($free, $this->target->minimum)) { 19 | return $this->makeResult( 20 | false, 21 | sprintf( 22 | $this->target->message, 23 | $this->target->path, 24 | bytes_to_human($free), 25 | $this->target->minimum 26 | ) 27 | ); 28 | } 29 | 30 | return $this->makeHealthyResult(); 31 | } 32 | 33 | public function isEnough($free, $minimum) 34 | { 35 | return $free > human_to_bytes($minimum); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Checkers/Docusign.php: -------------------------------------------------------------------------------- 1 | docusignIsNotInstalled()) { 20 | return $this->makeResult(false, $this->target->notInstalledMessage); 21 | } 22 | 23 | if (! $this->login()) { 24 | throw new DomainException( 25 | 'Unable to authenticate to the DocuSign Api' 26 | ); 27 | } 28 | 29 | return $this->makeHealthyResult(); 30 | } 31 | 32 | private function docusignIsNotInstalled() 33 | { 34 | return ! class_exists(ApiClient::class); 35 | } 36 | 37 | private function getAccountIdFromLogin($login) 38 | { 39 | return $login->getLoginAccounts()[0]->getAccountId(); 40 | } 41 | 42 | /** 43 | * @param $config 44 | * @return ApiClient 45 | */ 46 | protected function getApiClient($config) 47 | { 48 | return new ApiClient($config); 49 | } 50 | 51 | /** 52 | * @param $config 53 | * @return AuthenticationApi 54 | */ 55 | protected function getAuthApi($config) 56 | { 57 | return new AuthenticationApi($this->getApiClient($config)); 58 | } 59 | 60 | /** 61 | * @return ApiClient 62 | */ 63 | protected function getConfig() 64 | { 65 | return (new Configuration()) 66 | ->setDebug($this->target->debug) 67 | ->setDebugFile($this->makeFileName($this->target->debugFile)) 68 | ->setHost($this->target->apiHost) 69 | ->addDefaultHeader( 70 | 'X-DocuSign-Authentication', 71 | json_encode([ 72 | 'Username' => $this->target->username, 73 | 'Password' => $this->target->password, 74 | 'IntegratorKey' => $this->target->integratorKey, 75 | ]) 76 | ); 77 | } 78 | 79 | /** 80 | * @param $config 81 | * @return \DocuSign\eSign\Model\LoginInformation 82 | */ 83 | protected function getLoginInformation($config) 84 | { 85 | return $this->getAuthApi($config)->login($this->getLoginOptions()); 86 | } 87 | 88 | /** 89 | * @return LoginOptions 90 | */ 91 | protected function getLoginOptions() 92 | { 93 | return new LoginOptions(); 94 | } 95 | 96 | /** 97 | * @return mixed 98 | */ 99 | protected function login() 100 | { 101 | return $this->getAccountIdFromLogin( 102 | $this->getLoginInformation($this->getConfig()) 103 | ); 104 | } 105 | 106 | private function makeFileName($file) 107 | { 108 | if (is_absolute_path($file)) { 109 | return $file; 110 | } 111 | 112 | return base_path($file); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Checkers/Expression.php: -------------------------------------------------------------------------------- 1 | expressionIsOk() 17 | ? $this->makeHealthyResult() 18 | : $this->makeResult(false, $this->target->getErrorMessage()); 19 | } 20 | 21 | public function expressionIsOk() 22 | { 23 | $expressionResult = $this->executeExpression( 24 | $this->target->expressionValue 25 | ); 26 | 27 | if ($this->target->shouldReturn === true) { 28 | return (bool) $expressionResult; 29 | } 30 | 31 | if ($this->target->shouldReturn === false) { 32 | return ! $expressionResult; 33 | } 34 | 35 | return preg_match("|{$this->target->shouldReturn}|", $expressionResult); 36 | } 37 | 38 | public function executeExpression($expression) 39 | { 40 | return eval("return {$expression} ;"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Checkers/Extensions.php: -------------------------------------------------------------------------------- 1 | target->items); 17 | 18 | $installed = collect(get_loaded_extensions()); 19 | 20 | $alerts = $needed->reject(fn ($value) => $installed->contains($value)); 21 | 22 | if (count($alerts) === 0) { 23 | return $this->makeHealthyResult(); 24 | } 25 | 26 | $problems = collect($alerts)->implode(', '); 27 | 28 | return $this->makeResult( 29 | false, 30 | sprintf($this->target->getErrorMessage(), $problems) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Checkers/Filesystem.php: -------------------------------------------------------------------------------- 1 | temporaryFile( 16 | 'health-check-', 17 | 'just testing', 18 | storage_path() 19 | ); 20 | 21 | if (! file_exists($file)) { 22 | return $this->makeResult( 23 | false, 24 | sprintf($this->target->getErrorMessage(), $file) 25 | ); 26 | } 27 | 28 | unlink($file); 29 | 30 | return $this->makeHealthyResult(); 31 | } catch (\Exception $exception) { 32 | return $this->makeResultFromException($exception); 33 | } 34 | } 35 | 36 | /** 37 | * @param $name 38 | * @param $content 39 | * @param null $folder 40 | * @return string 41 | */ 42 | private function temporaryFile($name, $content, $folder = null) 43 | { 44 | $folder = $folder ?: sys_get_temp_dir(); 45 | 46 | $file = tempnam($folder, $name); 47 | 48 | file_put_contents($file, $content); 49 | 50 | register_shutdown_function(function () use ($file) { 51 | if (file_exists($file)) { 52 | unlink($file); 53 | } 54 | }); 55 | 56 | return $file; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Checkers/Framework.php: -------------------------------------------------------------------------------- 1 | makeHealthyResult(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Checkers/Health.php: -------------------------------------------------------------------------------- 1 | isHealthy(); 17 | 18 | return $this->makeResult( 19 | $healthy, 20 | $healthy ? '' : $this->target->getErrorMessage() 21 | ); 22 | } 23 | 24 | /** 25 | * Compute health. 26 | * 27 | * @param $previous 28 | * @param $resource 29 | * @return bool 30 | */ 31 | private function computeHealth($previous, $resource) 32 | { 33 | return $resource->isGlobal 34 | ? $previous 35 | : $previous && $this->computeHealthForAllTargets($resource); 36 | } 37 | 38 | /** 39 | * Compute health for targets. 40 | * 41 | * @param $resource 42 | * @return bool 43 | */ 44 | private function computeHealthForAllTargets($resource) 45 | { 46 | return $resource->targets->reduce(function ($carry, $target) { 47 | return $target->result->healthy; 48 | }, true); 49 | } 50 | 51 | /** 52 | * Check if the resource is healty. 53 | * 54 | * @return mixed 55 | */ 56 | protected function isHealthy() 57 | { 58 | $healthy = $this->target->resource->resources->reduce(function ( 59 | $carry, 60 | $item 61 | ) { 62 | return $this->computeHealth($carry, $item); 63 | }, 64 | true); 65 | 66 | return $healthy; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Checkers/HealthPanel.php: -------------------------------------------------------------------------------- 1 | getResourceUrlArray(); 40 | 41 | $first = collect($resources)->first(); 42 | 43 | if (filled($first)) { 44 | $this->target->setDisplay("{$first['url']}"); 45 | } 46 | 47 | try { 48 | foreach ($resources as $url) { 49 | [$healthy, $message] = $this->checkHealthPanel($url); 50 | 51 | if (! $healthy) { 52 | return $this->makeResult(false, $message); 53 | } 54 | } 55 | 56 | return $this->makeHealthyResult(); 57 | } catch (\Exception $exception) { 58 | report($exception); 59 | 60 | return $this->makeResultFromException($exception); 61 | } 62 | } 63 | 64 | /** 65 | * Get array of resource urls. 66 | * 67 | * @return array 68 | */ 69 | private function getResourceUrlArray() 70 | { 71 | $urls = $this->target->urls; 72 | 73 | if (! is_a($urls, Collection::class)) { 74 | $urls = collect($urls); 75 | } 76 | 77 | $result = collect(); 78 | 79 | $index = 0; 80 | 81 | foreach ($urls as $urlGroup) { 82 | foreach ($urlGroup as $url => $values) { 83 | if (blank($values['url'] ?? null)) { 84 | $values['url'] = $url; 85 | } 86 | 87 | $result[$index] = $values; 88 | 89 | $index++; 90 | } 91 | } 92 | 93 | return $result->toArray(); 94 | } 95 | 96 | /** 97 | * HTTP Checker. 98 | * 99 | * @return Result 100 | */ 101 | public function checkHealthPanel($url) 102 | { 103 | $resources = $this->getJson($url); 104 | 105 | if ($resources === null) { 106 | throw new \Exception('Error reading Health Panel json from '.$url['url']); 107 | } 108 | 109 | $messages = []; 110 | 111 | foreach ($resources as $resource) { 112 | foreach ($resource['targets'] as $target) { 113 | if (! $target['result']['healthy']) { 114 | $messages[] = $resource['name']; 115 | } 116 | } 117 | } 118 | 119 | if (count($messages) > 0) { 120 | throw new Exception('The following resources are failing: '.join(', ', $messages).'.'); 121 | } 122 | 123 | return [true, null]; 124 | } 125 | 126 | /** 127 | * Send an http request and fetch the panel json. 128 | * 129 | * @param $url 130 | * @return mixed|\Psr\Http\Message\ResponseInterface 131 | * 132 | * @throws \GuzzleHttp\Exception\GuzzleException 133 | */ 134 | private function fetchJson($url, $parameters = []) 135 | { 136 | $this->url = $url; 137 | 138 | return (new Guzzle())->request( 139 | $parameters['method'], 140 | $this->url, 141 | array_merge($this->getConnectionOptions($this->secure), $parameters) 142 | ); 143 | } 144 | 145 | public function getJson($parameters) 146 | { 147 | $url = $parameters['url']; 148 | 149 | unset($parameters['url']); 150 | 151 | return json_decode((string) $this->fetchJson($url, $parameters)->getBody(), true); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Checkers/Horizon.php: -------------------------------------------------------------------------------- 1 | horizonIsRunning() 17 | ? $this->makeHealthyResult() 18 | : $this->makeResult(false, $this->target->getErrorMessage()); 19 | } 20 | 21 | /** 22 | * Check if Horizon is up. 23 | * 24 | * @return bool 25 | */ 26 | protected function horizonIsRunning() 27 | { 28 | if (! $masters = app(MasterSupervisorRepository::class)->all()) { 29 | return false; 30 | } 31 | 32 | return collect($masters)->contains(function ($master) { 33 | return $master->status === 'paused'; 34 | }) 35 | ? false 36 | : true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Checkers/Https.php: -------------------------------------------------------------------------------- 1 | checkMail(); 26 | } 27 | 28 | /** 29 | * Configure mail for testing. 30 | */ 31 | private function configureMail() 32 | { 33 | $this->mailConfiguration = config('mail'); 34 | 35 | config(['mail' => $this->target->config->toArray()]); 36 | } 37 | 38 | /** 39 | * Send a test e-mail. 40 | */ 41 | private function checkMail() 42 | { 43 | $this->configureMail(); 44 | 45 | try { 46 | $this->sendMail(); 47 | 48 | $result = $this->makeHealthyResult(); 49 | } catch (\Exception $exception) { 50 | report($exception); 51 | 52 | $result = $this->makeResultFromException($exception); 53 | } 54 | 55 | $this->restoreMailConfiguration(); 56 | 57 | return $result; 58 | } 59 | 60 | /** 61 | * Restore mail configuration. 62 | */ 63 | private function restoreMailConfiguration() 64 | { 65 | config(['mail' => $this->mailConfiguration]); 66 | } 67 | 68 | /** 69 | * Send a test e-mail message. 70 | */ 71 | private function sendMail() 72 | { 73 | IlluminateMail::send($this->target->view, [], function ($message) { 74 | $fromAddress = Arr::get($this->target->config, 'from.address'); 75 | 76 | $message->returnPath($fromAddress); 77 | 78 | $message->cc($fromAddress); 79 | 80 | $message->bcc($fromAddress); 81 | 82 | $message->replyTo($fromAddress); 83 | 84 | $message->to($this->target->to); 85 | 86 | $message->subject($this->target->subject); 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Checkers/MixManifest.php: -------------------------------------------------------------------------------- 1 | checkFilePresence($file = $this->getManifestFileName()); 21 | 22 | $loaded = $this->loadJson($file); 23 | 24 | foreach ($loaded as $item => $asset) { 25 | if (! $this->ignored($item)) { 26 | $this->checkFilePresence(base_path($this->target->assetsRoot.$asset)); 27 | } 28 | } 29 | 30 | return $this->makeHealthyResult(); 31 | } 32 | 33 | /** 34 | * Get the mix manifest file name. 35 | * 36 | * @return string 37 | */ 38 | public function getManifestFileName() 39 | { 40 | $file = $this->target->file; 41 | 42 | if (Str::startsWith($file, '/')) { 43 | return $file; 44 | } 45 | 46 | return base_path($file); 47 | } 48 | 49 | /** 50 | * Check presence of a file in disk. 51 | * 52 | * @return string 53 | */ 54 | public function checkFilePresence($fileName) 55 | { 56 | if (! file_exists($fileName)) { 57 | throw new Exception("File doesn't exist: ".$fileName); 58 | } 59 | } 60 | 61 | /** 62 | * Load manifest. 63 | * 64 | * @return string 65 | */ 66 | public function loadJson($fileName) 67 | { 68 | $contents = file_get_contents($fileName); 69 | 70 | if (blank($fileName)) { 71 | throw new Exception('Manifest is empty or permission to read the file was denied: '.$fileName); 72 | } 73 | 74 | $contents = json_decode($contents, true); 75 | 76 | if (json_last_error() !== \JSON_ERROR_NONE) { 77 | throw new Exception('Error parsing manifest: '.$fileName.' - Error: '.$this->getJSONErrorMessage(json_last_error())); 78 | } 79 | 80 | return $contents; 81 | } 82 | 83 | /** 84 | * Snippet took from Symfony/Translation. 85 | * 86 | * Translates JSON_ERROR_* constant into meaningful message. 87 | */ 88 | private function getJSONErrorMessage(int $errorCode): string 89 | { 90 | switch ($errorCode) { 91 | case \JSON_ERROR_DEPTH: 92 | return 'Maximum stack depth exceeded'; 93 | case \JSON_ERROR_STATE_MISMATCH: 94 | return 'Underflow or the modes mismatch'; 95 | case \JSON_ERROR_CTRL_CHAR: 96 | return 'Unexpected control character found'; 97 | case \JSON_ERROR_SYNTAX: 98 | return 'Syntax error, malformed JSON'; 99 | case \JSON_ERROR_UTF8: 100 | return 'Malformed UTF-8 characters, possibly incorrectly encoded'; 101 | default: 102 | return 'Unknown error'; 103 | } 104 | } 105 | 106 | /** 107 | * Load manifest. 108 | * 109 | * @return string 110 | */ 111 | public function ignored($item) 112 | { 113 | if (! isset($this->target->ignoreItems)) { 114 | return false; 115 | } 116 | 117 | return $this->target->ignoreItems->contains($item); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Checkers/NotInDebugMode.php: -------------------------------------------------------------------------------- 1 | getCurrentUptime(); 20 | 21 | $this->persist($current); 22 | 23 | $rebooted = 24 | ($cs = $this->uptimeInSeconds($current)) < 25 | ($ss = $this->uptimeInSeconds($this->database)); 26 | 27 | return $this->makeResult( 28 | ! $rebooted, 29 | $this->makeMessage($current, $this->database) 30 | ); 31 | } 32 | 33 | /** 34 | * Execute command to get an uptime string. 35 | * 36 | * @return mixed|string 37 | * 38 | * @throws DomainException 39 | */ 40 | private function executeUptimeCommand() 41 | { 42 | $error = exec($this->resource->command, $system_string, $output); 43 | 44 | if ($output !== 0) { 45 | throw new DomainException((string) $error); 46 | } 47 | 48 | return (! is_array($system_string) || empty($system_string)) 49 | ? '' 50 | : $system_string[0]; 51 | } 52 | 53 | /** 54 | * Get current uptime. 55 | * 56 | * @return static 57 | * 58 | * @throws DomainException 59 | */ 60 | protected function getCurrentUptime() 61 | { 62 | return $this->parseUptimeString($this->executeUptimeCommand()); 63 | } 64 | 65 | /** 66 | * Normalize uptime matches. 67 | * 68 | * @param $matches 69 | * @return \Illuminate\Support\Collection 70 | */ 71 | protected function normalizeMatches($matches) 72 | { 73 | return collect($matches) 74 | ->filter(function ($item, $key) { 75 | return ! is_numeric($key); 76 | }) 77 | ->map(function ($item, $key) { 78 | $return = $item[0]; 79 | 80 | if (Str::startsWith($key, 'load')) { 81 | $return = floatval($return); 82 | } elseif (is_numeric($return)) { 83 | $return = (int) $return; 84 | } elseif (empty($return)) { 85 | $return = null; 86 | } 87 | 88 | return $return; 89 | }) 90 | ->toArray(); 91 | } 92 | 93 | /** 94 | * Parse the uptime string. 95 | * 96 | * @param $system_string 97 | * @return array 98 | */ 99 | protected function parseUptimeString($system_string) 100 | { 101 | $matches = []; 102 | 103 | preg_match( 104 | $this->resource['regex'], 105 | $system_string, 106 | $matches, 107 | PREG_OFFSET_CAPTURE 108 | ); 109 | 110 | $matches = $this->normalizeMatches($matches); 111 | 112 | $matches['uptime_string'] = $system_string; 113 | 114 | return $matches; 115 | } 116 | 117 | /** 118 | * Convert uptime to seconds. 119 | * 120 | * @param $date 121 | * @return int 122 | */ 123 | protected function uptimeInSeconds($date) 124 | { 125 | return 126 | (isset($date['up_days']) ? $date['up_days'] * 24 * 60 : 0) + 127 | (isset($date['up_hours']) ? $date['up_hours'] * 60 : 0) + 128 | (isset($date['up_minutes']) ? $date['up_minutes'] : 0); 129 | } 130 | 131 | /** 132 | * Make uptime message. 133 | * 134 | * @param $current 135 | * @param $saved 136 | * @return string 137 | */ 138 | protected function makeMessage($current, $saved = null) 139 | { 140 | $current = $this->toUptimeString($current); 141 | 142 | $saved = $this->toUptimeString($saved); 143 | 144 | return sprintf($this->target->getErrorMessage(), $current, $saved); 145 | } 146 | 147 | /** 148 | * Convert uptime to human readable string. 149 | * 150 | * @param $uptime 151 | * @return string 152 | */ 153 | public function toUptimeString($uptime) 154 | { 155 | return (string) CarbonInterval::days( 156 | isset($uptime['up_days']) ? $uptime['up_days'] : 0 157 | ) 158 | ->hours(isset($uptime['up_hours']) ? $uptime['up_hours'] : 0) 159 | ->minutes(isset($uptime['up_minutes']) ? $uptime['up_minutes'] : 0); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Checkers/Ping.php: -------------------------------------------------------------------------------- 1 | pingBinFactory(); 23 | 24 | $ipAddress = ip_address_from_hostname($this->target->hostname); 25 | 26 | $latency = $this->ping($ipAddress); 27 | 28 | $latencyFormatted = $latency ? "{$latency}ms" : 'error!'; 29 | 30 | $this->target->setDisplay( 31 | "{$this->target->name} ({$latencyFormatted})" 32 | ); 33 | 34 | if ($latency === false || $latency > $this->target->acceptedLatency) { 35 | $result = $this->makeResult( 36 | false, 37 | sprintf( 38 | $this->target->getErrorMessage(), 39 | $this->hosnameAndIp($this->target->hostname, $ipAddress), 40 | $latency === false ? '[ping error]' : $latency, 41 | $this->target->acceptedLatency 42 | ) 43 | ); 44 | } else { 45 | $result = $this->makeHealthyResult(); 46 | } 47 | 48 | return $result->setValue($latency)->setValueHuman("{$latency}ms"); 49 | } 50 | 51 | public function ping($hostname, $timeout = 5, $ttl = 128) 52 | { 53 | $this->host = $hostname; 54 | $this->ttl = $ttl; 55 | $this->timeout = $timeout; 56 | 57 | return $this->pingExec(); 58 | } 59 | 60 | /** 61 | * @param $hostname 62 | * @return mixed 63 | */ 64 | protected function hosnameAndIp($hostname, $ipAdress) 65 | { 66 | return $hostname.($hostname != $ipAdress ? " ({$ipAdress})" : ''); 67 | } 68 | 69 | /** 70 | * The exec method uses the possibly insecure exec() function, which passes 71 | * the input to the system. This is potentially VERY dangerous if you pass in 72 | * any user-submitted data. Be SURE you sanitize your inputs! 73 | * 74 | * @return int 75 | * Latency, in ms. 76 | */ 77 | private function pingExec() 78 | { 79 | $latency = false; 80 | 81 | $ttl = escapeshellcmd($this->ttl); 82 | $timeout = escapeshellcmd($this->timeout); 83 | $host = escapeshellcmd($this->host); 84 | 85 | // Exec string for Windows-based systems. 86 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 87 | // -n = number of pings; -i = ttl; -w = timeout (in milliseconds). 88 | $exec_string = 89 | $this->pingBin. 90 | ' -n 1 -i '. 91 | $ttl. 92 | ' -w '. 93 | $timeout * 1000 . 94 | ' '. 95 | $host; 96 | } elseif (strtoupper(PHP_OS) === 'DARWIN') { 97 | // Exec string for Darwin based systems (OS X). 98 | // -n = numeric output; -c = number of pings; -m = ttl; -t = timeout. 99 | $exec_string = 100 | $this->pingBin. 101 | ' -n -c 1 -m '. 102 | $ttl. 103 | ' -t '. 104 | $timeout. 105 | ' '. 106 | $host; 107 | } else { 108 | // Exec string for other UNIX-based systems (Linux). 109 | // -n = numeric output; -c = number of pings; -t = ttl; -W = timeout 110 | $exec_string = 111 | $this->pingBin. 112 | ' -n -c 1 -t '. 113 | $ttl. 114 | ' -W '. 115 | $timeout. 116 | ' '. 117 | $host. 118 | ' 2>&1'; 119 | } 120 | 121 | exec($exec_string, $output, $return); 122 | 123 | // Strip empty lines and reorder the indexes from 0 (to make results more 124 | // uniform across OS versions). 125 | $this->commandOutput = implode($output); 126 | $output = array_values(array_filter($output)); 127 | 128 | // If the result line in the output is not empty, parse it. 129 | if (! empty($output[1])) { 130 | // Search for a 'time' value in the result line. 131 | $response = preg_match( 132 | "/time(?:=|<)(?