├── src
├── config
│ ├── .gitkeep
│ ├── resources
│ │ ├── Framework.yml
│ │ ├── Horizon.yml
│ │ ├── Filesystem.yml
│ │ ├── Cache.yml
│ │ ├── Redis.yml
│ │ ├── RoutesCached.yml
│ │ ├── RedisConnectable.yml
│ │ ├── AppKey.yml
│ │ ├── MemcachedConnectable.yml
│ │ ├── ConfigurationCached.yml
│ │ ├── PostgreSqlConnectable.yml
│ │ ├── ElasticsearchConnectable.yml
│ │ ├── DiskSpace.yml
│ │ ├── S3.yml
│ │ ├── SecurityChecker.yml
│ │ ├── MySqlConnectable.yml
│ │ ├── Queue.yml
│ │ ├── DebugMode.yml
│ │ ├── Http.yml
│ │ ├── DirectoryPermissions.yml
│ │ ├── LocalStorage.yml
│ │ ├── EnvExists.yml
│ │ ├── LaravelServices.yml
│ │ ├── MigrationsUpToDate.yml
│ │ ├── RebootRequired.yml
│ │ ├── MixManifest.yml
│ │ ├── Sendinblue.yml
│ │ ├── MailgunConnectable.yml
│ │ ├── PackagesUpToDate.yml
│ │ ├── Database.yml
│ │ ├── DocuSign.yml
│ │ ├── CheckoutCom.yml
│ │ ├── HealthPanel.yml
│ │ ├── API.yml
│ │ ├── Certificate.yml
│ │ ├── Mail.yml
│ │ ├── SeeTickets.yml
│ │ ├── Latency.yml
│ │ ├── Adyen.yml
│ │ ├── ServerUptime.yml
│ │ ├── Broadcasting.yml
│ │ ├── ServerLoad.yml
│ │ ├── MySql.yml
│ │ ├── Sshd.yml
│ │ ├── Php.yml
│ │ ├── NginxServer.yml
│ │ ├── RedisServer.yml
│ │ ├── NewrelicDeamon.yml
│ │ ├── PostgreSqlServer.yml
│ │ ├── QueueWorkers.yml
│ │ ├── Supervisor.yml
│ │ ├── Dynamics.yml
│ │ ├── Https.yml
│ │ ├── ServerVars.yml
│ │ └── Extensions.yml
│ └── deprecated
│ │ └── Health.yml
├── resources
│ ├── views
│ │ ├── default
│ │ │ ├── email.blade.php
│ │ │ ├── panel.blade.php
│ │ │ ├── empty-panel.blade.php
│ │ │ ├── html.blade.php
│ │ │ └── partials
│ │ │ │ ├── style.blade.php
│ │ │ │ └── well.blade.php
│ │ └── html.blade.php
│ ├── sass
│ │ ├── _variables.scss
│ │ └── app.scss
│ ├── js
│ │ ├── bootstrap.js
│ │ ├── mixins
│ │ │ └── routes.js
│ │ ├── app.js
│ │ └── components
│ │ │ └── Chart.vue
│ └── dist
│ │ └── js
│ │ └── app.js.LICENSE.txt
├── Checkers
│ ├── Https.php
│ ├── Framework.php
│ ├── Contract.php
│ ├── Redis.php
│ ├── Extensions.php
│ ├── DiskSpace.php
│ ├── Writable.php
│ ├── Horizon.php
│ ├── Expression.php
│ ├── SecurityChecker.php
│ ├── CloudStorage.php
│ ├── Artisan.php
│ ├── ServerLoad.php
│ ├── Cache.php
│ ├── Queue.php
│ ├── Filesystem.php
│ ├── Health.php
│ ├── PortCheck.php
│ ├── Mail.php
│ ├── Composer.php
│ ├── Database.php
│ ├── Docusign.php
│ ├── ServerVars.php
│ ├── MixManifest.php
│ ├── Broadcasting.php
│ ├── Process.php
│ ├── HealthPanel.php
│ ├── NotInDebugMode.php
│ ├── ServerUptime.php
│ ├── Certificate.php
│ ├── Ping.php
│ ├── Base.php
│ └── DirectoryAndFilePresence.php
├── Support
│ ├── Constants.php
│ ├── Traits
│ │ ├── HandleExceptions.php
│ │ ├── ImportProperties.php
│ │ ├── Routing.php
│ │ ├── ToArray.php
│ │ └── Database.php
│ ├── Jobs
│ │ └── TestJob.php
│ ├── Broadcasting
│ │ ├── server.js
│ │ └── pusher.blade.php
│ ├── LocallyProtected.php
│ ├── Timer.php
│ ├── Result.php
│ ├── Cache.php
│ ├── helpers.php
│ ├── ResourceLoader.php
│ └── Target.php
├── Events
│ ├── RaiseHealthIssue.php
│ └── HealthPing.php
├── Http
│ ├── Middleware
│ │ └── LocallyProtected.php
│ └── Controllers
│ │ ├── Broadcasting.php
│ │ └── Health.php
├── Console
│ └── Commands
│ │ ├── HealthPanelCommand.php
│ │ └── HealthCheckCommand.php
├── Data
│ └── Models
│ │ └── HealthCheck.php
├── database
│ └── migrations
│ │ └── 2018_09_09_000001_create_table_health_checks.php
├── Listeners
│ └── NotifyHealthIssue.php
├── Service.php
└── Notifications
│ └── HealthStatus.php
├── .styleci.yml
├── .scrutinizer.yml
├── docs
└── images
│ ├── json.png
│ ├── panel.png
│ ├── slack.png
│ ├── error-alert.png
│ ├── error-hint.png
│ ├── error-multi.png
│ ├── console-panel.png
│ ├── json-resource.png
│ ├── error-single-2-columns.png
│ └── error-single-4-columns.png
├── .gitignore
├── mix-manifest.json
├── .github
├── dependabot.yml
└── workflows
│ └── run-tests.yml
├── .prettierrc.yml
├── tests
└── PhpUnit
│ ├── Service
│ ├── TimerTest.php
│ └── ServiceTest.php
│ ├── bootstrap.php
│ └── TestCase.php
├── webpack.mix.js
├── .travis.yml
├── phpunit.xml
├── package.json
├── LICENSE.md
├── composer.json
└── CHANGELOG.md
/src/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 |
--------------------------------------------------------------------------------
/src/resources/views/default/email.blade.php:
--------------------------------------------------------------------------------
1 | This is a health check email message.
2 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | build:
2 | environment:
3 | php:
4 | version: 7.2
5 |
--------------------------------------------------------------------------------
/docs/images/json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/json.png
--------------------------------------------------------------------------------
/docs/images/panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/panel.png
--------------------------------------------------------------------------------
/docs/images/slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/slack.png
--------------------------------------------------------------------------------
/docs/images/error-alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/error-alert.png
--------------------------------------------------------------------------------
/docs/images/error-hint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/error-hint.png
--------------------------------------------------------------------------------
/docs/images/error-multi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/error-multi.png
--------------------------------------------------------------------------------
/docs/images/console-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/console-panel.png
--------------------------------------------------------------------------------
/docs/images/json-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/json-resource.png
--------------------------------------------------------------------------------
/docs/images/error-single-2-columns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/error-single-2-columns.png
--------------------------------------------------------------------------------
/docs/images/error-single-4-columns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/health/HEAD/docs/images/error-single-4-columns.png
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/config/resources/Framework.yml:
--------------------------------------------------------------------------------
1 | name: Framework
2 | abbreviation: frmwrk
3 | checker: PragmaRX\Health\Checkers\Framework
4 | notify: true
5 | column_size: 3
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/Checkers/Https.php:
--------------------------------------------------------------------------------
1 |
5 |
9 |
10 |
11 | @stop
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/config/resources/Cache.yml:
--------------------------------------------------------------------------------
1 | name: Cache
2 | abbreviation: csh
3 | checker: PragmaRX\Health\Checkers\Cache
4 | notify: true
5 | error_message: "Cache is not returning cached values."
6 | column_size: 3
7 | targets:
8 | - default:
9 | seconds: 60
10 | key: health-cache-test
11 |
--------------------------------------------------------------------------------
/src/config/resources/Redis.yml:
--------------------------------------------------------------------------------
1 | name: Redis
2 | abbreviation: rds
3 | checker: PragmaRX\Health\Checkers\Redis
4 | column_size: 3
5 | notify: true
6 | error_message: "Got a wrong value back from Redis."
7 | targets:
8 | - default:
9 | key: "health:redis:key"
10 | connection: ""
11 |
--------------------------------------------------------------------------------
/src/resources/views/html.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ config('health.title') }}
7 |
8 | @yield('head')
9 |
10 |
11 |
12 | @yield('body')
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Checkers/Framework.php:
--------------------------------------------------------------------------------
1 | makeHealthyResult();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/config/resources/RoutesCached.yml:
--------------------------------------------------------------------------------
1 | name: Routes Cached
2 | abbreviation: rtcch
3 | checker: PragmaRX\Health\Checkers\Expression
4 | notify: true
5 | column_size: 3
6 | error_message: "Routes are not cached"
7 | targets:
8 | - default:
9 | expression_value: "app()->routesAreCached()"
10 | should_return: true
11 |
--------------------------------------------------------------------------------
/src/config/resources/RedisConnectable.yml:
--------------------------------------------------------------------------------
1 | name: Redis Connectable
2 | abbreviation: redisconn
3 | checker: PragmaRX\Health\Checkers\PortCheck
4 | notify: true
5 | column_size: 3
6 | error_message: "Could not connect to %s on port %s"
7 | targets:
8 | - default:
9 | hostname: localhost
10 | port: 6379
11 | timeout: 2
12 |
--------------------------------------------------------------------------------
/src/config/resources/AppKey.yml:
--------------------------------------------------------------------------------
1 | name: App Key
2 | abbreviation: appkey
3 | checker: PragmaRX\Health\Checkers\Expression
4 | notify: true
5 | column_size: 3
6 | error_message: "There is no app key defined"
7 | graph_enabled: false
8 | targets:
9 | - default:
10 | expression_value: "config('app.key') !== null"
11 | should_return: true
12 |
--------------------------------------------------------------------------------
/src/config/resources/MemcachedConnectable.yml:
--------------------------------------------------------------------------------
1 | name: Memcached Connectable
2 | abbreviation: redisconn
3 | checker: PragmaRX\Health\Checkers\PortCheck
4 | notify: true
5 | column_size: 3
6 | error_message: "Could not connect to %s on port %s"
7 | targets:
8 | - default:
9 | hostname: localhost
10 | port: 11211
11 | timeout: 2
12 |
--------------------------------------------------------------------------------
/src/config/resources/ConfigurationCached.yml:
--------------------------------------------------------------------------------
1 | name: Configuration Cached
2 | abbreviation: cfgcch
3 | checker: PragmaRX\Health\Checkers\Expression
4 | notify: true
5 | column_size: 3
6 | error_message: "Configuration is not cached"
7 | targets:
8 | - default:
9 | expression_value: "app()->configurationIsCached()"
10 | should_return: true
11 |
--------------------------------------------------------------------------------
/src/config/resources/PostgreSqlConnectable.yml:
--------------------------------------------------------------------------------
1 | name: PostgreSQL Connectable
2 | abbreviation: pstgrsqlsrvrconn
3 | checker: PragmaRX\Health\Checkers\PortCheck
4 | notify: true
5 | column_size: 3
6 | error_message: "Could not connect to %s on port %s"
7 | targets:
8 | - default:
9 | hostname: localhost
10 | port: 5432
11 | timeout: 2
12 |
--------------------------------------------------------------------------------
/src/config/resources/ElasticsearchConnectable.yml:
--------------------------------------------------------------------------------
1 | name: Elasticsearch Connectable
2 | abbreviation: redisconn
3 | checker: PragmaRX\Health\Checkers\PortCheck
4 | notify: true
5 | column_size: 3
6 | error_message: "Could not connect to %s on port %s"
7 | targets:
8 | - localhost:
9 | hostname: localhost
10 | port: 9200
11 | timeout: 2
12 |
--------------------------------------------------------------------------------
/src/config/resources/DiskSpace.yml:
--------------------------------------------------------------------------------
1 | name: Disk Space
2 | abbreviation: dskspc
3 | checker: PragmaRX\Health\Checkers\DiskSpace
4 | notify: true
5 | column_size: 3
6 | targets:
7 | - default:
8 | name: root
9 | path: "/"
10 | minimum: 1GB
11 | message:
12 | "Volume %s is getting out of space. It has %s when it should have at least %s."
13 |
--------------------------------------------------------------------------------
/src/config/resources/S3.yml:
--------------------------------------------------------------------------------
1 | name: S3
2 | abbreviation: s3
3 | checker: PragmaRX\Health\Checkers\CloudStorage
4 | notify: true
5 | column_size: 3
6 | error_message: "Amazon S3 connection is failing."
7 | targets:
8 | - default:
9 | driver: s3
10 | file: pragmarx-health-s3-testfile.txt
11 | contents: "{{ \\Illuminate\\Support\\Str::random(32) }}"
12 |
--------------------------------------------------------------------------------
/src/config/resources/SecurityChecker.yml:
--------------------------------------------------------------------------------
1 | name: Packages Security Checker
2 | abbreviation: debug
3 | checker: PragmaRX\Health\Checkers\SecurityChecker
4 | notify: true
5 | column_size: 3
6 | executable: /usr/local/bin/local-php-security-checker
7 | error_message:
8 | "The following packages have vulnerabilities referenced in the SensioLabs security advisories database: %s"
9 |
--------------------------------------------------------------------------------
/src/config/resources/MySqlConnectable.yml:
--------------------------------------------------------------------------------
1 | name: MySQL Connectable
2 | abbreviation: mysqlgrsqlsrvrconn
3 | checker: PragmaRX\Health\Checkers\PortCheck
4 | notify: true
5 | column_size: 3
6 | error_message: "Could not connect to %s on port %s"
7 | targets:
8 | - default:
9 | name: localhost
10 | hostname: localhost
11 | port: 3306
12 | timeout: 2
13 |
--------------------------------------------------------------------------------
/src/config/resources/Queue.yml:
--------------------------------------------------------------------------------
1 | name: Queue
2 | abbreviation: queue
3 | checker: PragmaRX\Health\Checkers\Queue
4 | error_message: "Queue system is not working properly."
5 | column_size: 3
6 | targets:
7 | - default:
8 | test_job: PragmaRX\Health\Support\Jobs\TestJob
9 | cache_instance: cache
10 | notify: true
11 | connection: ""
12 | queue: ""
13 |
--------------------------------------------------------------------------------
/src/config/resources/DebugMode.yml:
--------------------------------------------------------------------------------
1 | name: Debug Mode on Production
2 | abbreviation: debug
3 | checker: PragmaRX\Health\Checkers\Expression
4 | notify: true
5 | column_size: 3
6 | error_message: "Application is in debug mode"
7 | targets:
8 | - default:
9 | expression_value: "!app()->environment('production') || !app('config')->get('app.debug')"
10 | should_return: true
11 |
--------------------------------------------------------------------------------
/src/config/resources/Http.yml:
--------------------------------------------------------------------------------
1 | name: Http
2 | abbreviation: http
3 | checker: PragmaRX\Health\Checkers\Http
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 30
8 | roundtrip_timeout: 30
9 | targets:
10 | - default:
11 | urls:
12 | - '{{ config("app.url") }}'
13 | - http://google.com
14 |
--------------------------------------------------------------------------------
/src/config/resources/DirectoryPermissions.yml:
--------------------------------------------------------------------------------
1 | name: Directory Permissions
2 | abbreviation: dirperm
3 | checker: PragmaRX\Health\Checkers\Writable
4 | notify: true
5 | column_size: 3
6 | error_message: "%s is not writable"
7 | targets:
8 | - default:
9 | paths:
10 | - "{{ storage_path() }}"
11 | - "{{ storage_path('logs/laravel.log') }}"
12 | - "{{ base_path('bootstrap/cache') }}"
13 |
--------------------------------------------------------------------------------
/src/config/resources/LocalStorage.yml:
--------------------------------------------------------------------------------
1 | name: LocalStorage
2 | abbreviation: lclstrg
3 | checker: PragmaRX\Health\Checkers\CloudStorage
4 | notify: true
5 | error_message: "Cloud storage is not retrieving files correctly."
6 | column_size: 3
7 | targets:
8 | - default:
9 | driver: local
10 | file: pragmarx-health-storage-testfile.txt
11 | contents: "{{ \\Illuminate\\Support\\Str::random(32) }}"
12 |
--------------------------------------------------------------------------------
/src/config/resources/EnvExists.yml:
--------------------------------------------------------------------------------
1 | name: .env Exists
2 | abbreviation: envexists
3 | checker: PragmaRX\Health\Checkers\DirectoryAndFilePresence
4 | notify: true
5 | error_message: "The .env file does not exists"
6 | column_size: 3
7 | targets:
8 | - default:
9 | file_exists:
10 | - "{{ base_path('.env') }}"
11 | file_do_not_exists:
12 | directory_exists:
13 | directory_do_not_exists:
14 |
--------------------------------------------------------------------------------
/src/config/resources/LaravelServices.yml:
--------------------------------------------------------------------------------
1 | name: LaravelServices
2 | abbreviation: lvs
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | connection_timeout: 2
7 | roundtrip_timeout: 4
8 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s."
9 | targets:
10 | - default:
11 | urls:
12 | - "https://forge.laravel.com"
13 | - "https://envoyer.io"
14 |
--------------------------------------------------------------------------------
/tests/PhpUnit/Service/TimerTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(1, (int) Timer::stop());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/config/resources/MigrationsUpToDate.yml:
--------------------------------------------------------------------------------
1 | name: Migrations Up to Date
2 | abbreviation: debug
3 | checker: PragmaRX\Health\Checkers\Artisan
4 | notify: true
5 | column_size: 3
6 | error_message: "Not all migrations were migrated"
7 | targets:
8 | - default:
9 | command:
10 | name: "migrate"
11 | options:
12 | "--pretend": true
13 | "--force": true
14 | should_return: "Nothing to migrate"
15 |
--------------------------------------------------------------------------------
/src/config/resources/RebootRequired.yml:
--------------------------------------------------------------------------------
1 | name: RebootRequired
2 | abbreviation: rbtrqrd
3 | checker: PragmaRX\Health\Checkers\DirectoryAndFilePresence
4 | notify: true
5 | error_message: "A reboot is required in this server (Uptime Checker)"
6 | column_size: 3
7 | targets:
8 | - default:
9 | file_exists:
10 | file_do_not_exists:
11 | - /var/run/reboot-required
12 | directory_exists:
13 | directory_do_not_exists:
14 |
--------------------------------------------------------------------------------
/src/resources/sass/_variables.scss:
--------------------------------------------------------------------------------
1 |
2 | // Body
3 | $body-bg: #f8fafc;
4 |
5 | // Typography
6 | $font-family-sans-serif: "Nunito", sans-serif;
7 | $font-size-base: 0.9rem;
8 | $line-height-base: 1.6;
9 |
10 | // Colors
11 | $blue: #635321;
12 | $green: #E5BBFF;
13 | $cyan: #FFF4FC;
14 | $orange: #E89DB4;
15 | $red: #FFA8A4;
16 |
17 | $indigo: #6574cd;
18 | $purple: #9561e2;
19 | $pink: #f66D9b;
20 | $yellow: #ffed4a;
21 | $teal: #4dc0b5;
22 |
--------------------------------------------------------------------------------
/src/Checkers/Contract.php:
--------------------------------------------------------------------------------
1 | {
5 | return route.name === name
6 | })
7 |
8 | let routeUri = route.uri
9 |
10 | Object.entries(params).forEach(([key, value]) => {
11 | routeUri = routeUri.replace('{'+key+'}', value)
12 | })
13 |
14 | return routeUri
15 | },
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/Support/Traits/HandleExceptions.php:
--------------------------------------------------------------------------------
1 | failure = $failure;
25 |
26 | $this->channel = $channel;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Support/Jobs/TestJob.php:
--------------------------------------------------------------------------------
1 | each(function ($value, $key) {
17 | $key = Str::camel($key);
18 |
19 | if (! property_exists($this, $key)) {
20 | $this->$key = $value;
21 | }
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/config/resources/Database.yml:
--------------------------------------------------------------------------------
1 | name: Database
2 | abbreviation: db
3 | checker: PragmaRX\Health\Checkers\Database
4 | notify: true
5 | column_size: 3
6 | targets:
7 | - users:
8 | type: "find_first_model"
9 | models:
10 | - "{{ config('auth.providers.users.model') }}"
11 | - "query speed":
12 | type: "raw_query"
13 | connection: "default"
14 | query: "select * from users u join password_resets pr on pr.email = u.email order by u.created_at desc"
15 | maximum_time: 0.05
16 | error_message: "Query took %sms when it should have last at most %sms"
17 |
--------------------------------------------------------------------------------
/src/config/resources/DocuSign.yml:
--------------------------------------------------------------------------------
1 | name: DocuSign
2 | abbreviation: dcsgn
3 | checker: PragmaRX\Health\Checkers\Docusign
4 | column_size: 3
5 | notify: true
6 | error_message: "A reboot is required in this server (Uptime Checker)"
7 | targets:
8 | - default:
9 | username: "{{ docusign.username }}"
10 | password: "{{ docusign.password }}"
11 | integrator_key: "{{ docusign.integrator_key }}"
12 | debug: "{{ docusign.debug }}"
13 | debug_file: storage/logs/docusign.log
14 | api_host: "{{ docusign.host }}"
15 | not_installed_message: "Docusign is not installed."
16 |
--------------------------------------------------------------------------------
/src/resources/views/default/empty-panel.blade.php:
--------------------------------------------------------------------------------
1 | @extends('pragmarx/health::default.html')
2 |
3 | @section('html.body')
4 |
5 |
6 |
7 |
{{ config('health.title') }}
8 |
9 |
10 |
11 |
12 | You list of resources is empty or something wrong happened, please check your logs.
13 |
14 |
15 |
16 |
17 | @stop
18 |
--------------------------------------------------------------------------------
/src/config/resources/CheckoutCom.yml:
--------------------------------------------------------------------------------
1 | name: Checkout.com API
2 | abbreviation: https
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 30
8 | roundtrip_timeout: 30
9 | targets:
10 | - default:
11 | urls:
12 | - checkout-com:
13 | url: "https://{{ config('services.checkout_com.api.host') }}/event-types"
14 | method: GET
15 | headers:
16 | Authorization: "{{ config('services.checkout_com.api.keys.secret') }}"
17 |
--------------------------------------------------------------------------------
/src/Support/Broadcasting/server.js:
--------------------------------------------------------------------------------
1 | var request = require('request')
2 | var server = require('http').Server()
3 | var io = require('socket.io')(server)
4 | var Redis = require('ioredis')
5 | var redis = new Redis()
6 |
7 | redis.subscribe('pragmarx-health-broadcasting-channel')
8 |
9 | redis.on('message', function(channel, message) {
10 | message = JSON.parse(message)
11 |
12 | console.log('ponging...')
13 |
14 | if (message.event == 'PragmaRX\\Health\\Events\\HealthPing') {
15 | request.get(
16 | message.data.callbackUrl + '?data=' + JSON.stringify(message.data),
17 | )
18 | }
19 | })
20 |
21 | server.listen(3000)
22 |
--------------------------------------------------------------------------------
/src/config/resources/HealthPanel.yml:
--------------------------------------------------------------------------------
1 | name: Remote Laravel Health Panel
2 | abbreviation: health-panel
3 | checker: PragmaRX\Health\Checkers\HealthPanel
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 5
8 | roundtrip_timeout: 10
9 | targets:
10 | - default:
11 | urls:
12 | - "http://your-site.com/health/check":
13 | method: GET
14 | auth:
15 | - "{{ config('services.whatever.api.username') }}"
16 | - "{{ config('services.whatever.api.password') }}"
17 | - basic
18 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix')
2 |
3 | /*
4 | |--------------------------------------------------------------------------
5 | | Mix Asset Management
6 | |--------------------------------------------------------------------------
7 | |
8 | | Mix provides a clean, fluent API for defining some Webpack build steps
9 | | for your Laravel application. By default, we are compiling the Sass
10 | | file for the application as well as bundling up all the JS files.
11 | |
12 | */
13 |
14 | mix.js('src/resources/js/app.js', 'src/resources/dist/js').sass(
15 | 'src/resources/sass/app.scss',
16 | 'src/resources/dist/css'
17 | )
18 |
--------------------------------------------------------------------------------
/src/config/resources/API.yml:
--------------------------------------------------------------------------------
1 | name: API Checker
2 | abbreviation: apichecker
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 5
8 | roundtrip_timeout: 10
9 | targets:
10 | - default:
11 | urls:
12 | - https://api.whatever.com/v1.1/authorization/token:
13 | method: POST
14 | auth:
15 | - "{{ config('services.whatever.api.username') }}"
16 | - "{{ config('services.whatever.api.password') }}"
17 | - basic
18 | is_json: true
19 |
--------------------------------------------------------------------------------
/src/config/resources/Certificate.yml:
--------------------------------------------------------------------------------
1 | name: Certificate
2 | abbreviation: ccert
3 | checker: PragmaRX\Health\Checkers\Certificate
4 | notify: true
5 | error_message: "Invalid certificate for domain: %s"
6 | column_size: 3
7 | command: "openssl s_client {$options} -connect {$host}:443 2>&1"
8 | verify_string: "Verify return code"
9 | success_string: "Verify return code: 0 (ok)"
10 | targets:
11 | - host1:
12 | options: -tls1
13 | urls:
14 | - laravel.com
15 | - wwwprd:
16 | options: -tls1_1
17 | urls:
18 | - google.com
19 | - revoked:
20 | options: -tls1_2
21 | urls:
22 | - revoked-rsa-dv.ssl.com
23 |
--------------------------------------------------------------------------------
/src/config/resources/Mail.yml:
--------------------------------------------------------------------------------
1 | name: Mail
2 | abbreviation: ml
3 | checker: PragmaRX\Health\Checkers\Mail
4 | notify: true
5 | column_size: 3
6 | targets:
7 | - default:
8 | view: "pragmarx/health::default.email"
9 | config:
10 | driver: log
11 | host: mailtrap.io
12 | port: "2525"
13 | from:
14 | address: health@example.com
15 | name: "Health Checker"
16 | encryption: null
17 | username: "{{ mail.username }}"
18 | password: "{{ mail.password }}"
19 | sendmail: "/usr/sbin/sendmail -bs"
20 | to: you-know-who@sink.sendgrid.net
21 | subject: "Health Test mail"
22 |
--------------------------------------------------------------------------------
/src/config/resources/SeeTickets.yml:
--------------------------------------------------------------------------------
1 | name: See Tickets API
2 | abbreviation: seetickets
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 5
8 | roundtrip_timeout: 10
9 | targets:
10 | - default:
11 | urls:
12 | - url1:
13 | url: "{{ config('services.see_tickets.api.endpoint') }}"
14 | method: POST
15 | auth:
16 | - "{{ config('services.see_tickets.api.username') }}"
17 | - "{{ config('services.see_tickets.api.password') }}"
18 | - basic
19 |
--------------------------------------------------------------------------------
/src/config/resources/Latency.yml:
--------------------------------------------------------------------------------
1 | name: Latency
2 | abbreviation: latency
3 | checker: PragmaRX\Health\Checkers\Ping
4 | notify: true
5 | binary: "{{ config('health.services.ping.bin') }}"
6 | error_message:
7 | 'The host "%s" exceeded the maximum accepted latency on ping: last ping was %s, accepted is %s'
8 | column_size: 3
9 | targets:
10 | - server:
11 | name: rio de janeiro servers
12 | hostname: google.com
13 | accepted_latency: 15
14 | - server:
15 | name: south america servers
16 | hostname: globo.com
17 | accepted_latency: 20
18 | - server:
19 | name: europe servers
20 | hostname: ovh.fr
21 | accepted_latency: 2
22 |
--------------------------------------------------------------------------------
/src/Http/Middleware/LocallyProtected.php:
--------------------------------------------------------------------------------
1 | check($request)) {
21 | abort(403, 'Forbidden');
22 | }
23 |
24 | return $next($request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Checkers/Redis.php:
--------------------------------------------------------------------------------
1 | target->key,
21 | $number = random_bytes(80)
22 | );
23 |
24 | $result = IlluminateRedis::get($key);
25 |
26 | return $this->makeResult(
27 | $number == $result,
28 | $number !== $result ? $this->target->key : ''
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/config/resources/Adyen.yml:
--------------------------------------------------------------------------------
1 | name: Adyen
2 | abbreviation: adyen
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 20
8 | roundtrip_timeout: 30
9 | targets:
10 | - default:
11 | urls:
12 | - url1:
13 | url: "{{ config('services.adyen.api_base_url') }}"
14 | method: POST
15 | debug: false
16 | headers:
17 | x-api-key: "{{ config('services.adyen.backend.api_key') }}"
18 | content-type: application/json
19 | form_params:
20 | merchantAccount: "{{ config('services.adyen.merchant_account') }}"
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/resources/views/default/html.blade.php:
--------------------------------------------------------------------------------
1 | @extends('pragmarx/health::html')
2 |
3 | @section('head')
4 |
7 |
8 |
11 | @stop
12 |
13 | @section('body')
14 | @include('pragmarx/health::default.partials.style')
15 |
16 | @yield('html.header')
17 |
18 | @yield('html.body')
19 |
20 | @yield('html.footer')
21 |
22 |
25 |
26 | @if (config('app.env') == 'local')
27 |
28 | @endif
29 | @stop
30 |
--------------------------------------------------------------------------------
/src/config/resources/ServerUptime.yml:
--------------------------------------------------------------------------------
1 | name: ServerUptime
2 | abbreviation: uptm
3 | checker: PragmaRX\Health\Checkers\ServerUptime
4 | column_size: 3
5 | notify: true
6 | error_message:
7 | 'Looks like your server was recently rebooted, current uptime is now "%s" and it was "%s" before restart.'
8 | targets:
9 | - default:
10 | regex:
11 | '~(?\d{1,2}):(?\d{2})(?::(?\d{2}))?\s+up\s+(?:(?\d+)\s+days?,\s+)?\b(?:(?\d+):)?(?\d+)(?:\s+(?:minute|minutes|min)?)?,\s+(?\d+)?.+?(?\d+.\d+),?\s+(?\d+.\d+),?\s+(?\d+.\d+)~'
12 | command: "uptime 2>&1"
13 | save_to: "{{ storage_path('app') }}/uptime.json"
14 | action_message: "Your server was rebooted (Uptime Checker)"
15 |
--------------------------------------------------------------------------------
/src/config/resources/Broadcasting.yml:
--------------------------------------------------------------------------------
1 | name: Broadcasting
2 | abbreviation: brdc
3 | checker: PragmaRX\Health\Checkers\Broadcasting
4 | notify: true
5 | column_size: 3
6 | error_message:
7 | "The broadcasting service did not respond in time, it may be in trouble."
8 | targets:
9 | - default:
10 | channel: pragmarx-health-broadcasting-channel
11 | route_name: pragmarx.health.broadcasting.callback
12 | secret: "{{ \\Illuminate\\Support\\Str::random(32) }}"
13 | timeout: 30
14 | routes:
15 | pragmarx.health.broadcasting.callback:
16 | uri: "/health/broadcasting/callback/{secret}"
17 | controller: PragmaRX\Health\Http\Controllers\Broadcasting
18 | action: callback
19 | save_to: "{{ storage_path('app') }}/broadcasting.json"
20 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Broadcasting.php:
--------------------------------------------------------------------------------
1 | get('data'), true)['resource'];
13 |
14 | $checker = $this->instantiateChecker($resource);
15 |
16 | $checker->pong($secret);
17 | }
18 |
19 | /**
20 | * @param $resource
21 | * @return mixed
22 | */
23 | protected function instantiateChecker($resource)
24 | {
25 | $checkerClass = $resource['checker'];
26 |
27 | $checker = new $checkerClass($resource, []);
28 |
29 | return $checker;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/resources/views/default/partials/style.blade.php:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/src/config/resources/ServerLoad.yml:
--------------------------------------------------------------------------------
1 | name: ServerLoad
2 | abbreviation: load
3 | checker: PragmaRX\Health\Checkers\ServerLoad
4 | error_message:
5 | 'Your server might be overloaded, current server load values are "%s, %s and %s", which are above the threshold values: "%s, %s and %s".'
6 | column_size: 3
7 | notify: true
8 | targets:
9 | - default:
10 | regex:
11 | '~(?\d{1,2}):(?\d{2})(?::(?\d{2}))?\s+up\s+(?:(?\d+)\s+days?,\s+)?\b(?:(?\d+):)?(?\d+)(?:\s+(?:minute|minutes|min)?)?,\s+(?\d+).+?(?\d+.\d+),?\s+(?\d+.\d+),?\s+(?\d+.\d+)~'
12 | command: "uptime 2>&1"
13 | max_load:
14 | load_1: 2
15 | load_5: 1.5
16 | load_15: 1
17 | action_message: "Too much load! (Server Load Checker)"
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/config/resources/MySql.yml:
--------------------------------------------------------------------------------
1 | name: MySql
2 | abbreviation: msql
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: mysqld
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 20
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/Sshd.yml:
--------------------------------------------------------------------------------
1 | name: Sshd
2 | abbreviation: sshd
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: sshd
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 15
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/Support/Broadcasting/pusher.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pusher Test
5 |
6 |
21 |
22 |
23 |
24 | Pusher waiting for events...
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/config/resources/Php.yml:
--------------------------------------------------------------------------------
1 | name: Php
2 | abbreviation: php
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: php-fpm
11 | pid_file: /tmp/php7.1-fpm.pid
12 | instances:
13 | minimum:
14 | count: 2
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 20
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/NginxServer.yml:
--------------------------------------------------------------------------------
1 | name: NginxServer
2 | abbreviation: ngnxsrvr
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: nginx
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 4
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 8
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/RedisServer.yml:
--------------------------------------------------------------------------------
1 | name: RedisServer
2 | abbreviation: rdssrvr
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: redis-server
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 20
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/Console/Commands/HealthPanelCommand.php:
--------------------------------------------------------------------------------
1 | info('Checking resources and gathering information to build the panel...');
30 |
31 | return $commands->panel($this);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/config/resources/NewrelicDeamon.yml:
--------------------------------------------------------------------------------
1 | name: NewrelicDeamon
2 | abbreviation: nwrlcdmn
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: newrelic-daemon
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 4
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/PostgreSqlServer.yml:
--------------------------------------------------------------------------------
1 | name: PostgreSqlServer
2 | abbreviation: pstgrsqlsrvr
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "pgrep %s"
9 | method: process_count
10 | process_name: postgres
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 4
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 8
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/QueueWorkers.yml:
--------------------------------------------------------------------------------
1 | name: QueueWorkers
2 | abbreviation: qwrkrs
3 | checker: PragmaRX\Health\Checkers\Process
4 | notify: true
5 | column_size: 3
6 | targets:
7 | - default:
8 | command: "ps aux | grep php | grep queue:work"
9 | method: process_count
10 | process_name: php
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 3
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/config/resources/Supervisor.yml:
--------------------------------------------------------------------------------
1 | name: Supervisor
2 | abbreviation: sprvsr
3 | checker: PragmaRX\Health\Checkers\Process
4 | column_size: 3
5 | notify: true
6 | targets:
7 | - default:
8 | command: "ps aux | grep python | grep supervisord"
9 | method: process_count
10 | process_name: supervisor
11 | pid_file: ""
12 | instances:
13 | minimum:
14 | count: 1
15 | message:
16 | 'Process "%s" has not enough instances running: it has %s, when should have at least %s'
17 | maximum:
18 | count: 3
19 | message:
20 | 'Process "%s" exceeded the maximum number of running instances: it has %s, when should have at most %s'
21 | pid_file_missing_error_message: "Process ID file is missing: %s."
22 | pid_file_missing_not_locked:
23 | "Process ID file is not being used by any process: %s."
24 |
--------------------------------------------------------------------------------
/src/Console/Commands/HealthCheckCommand.php:
--------------------------------------------------------------------------------
1 | info('Checking resources...');
33 |
34 | return $commands->check($this);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/config/resources/Dynamics.yml:
--------------------------------------------------------------------------------
1 | name: Dynamics 365
2 | abbreviation: dynamics
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 5
8 | roundtrip_timeout: 10
9 | targets:
10 | - default:
11 | urls:
12 | - url-from-config:
13 | url: "{{ config('services.newsletter.login_url') }}"
14 | headers:
15 | api-key: "{{ config('services.sendinblue.api_key') }}"
16 | method: POST
17 | form_params:
18 | username: "{{ config('services.newsletter.username') }}"
19 | resource: "{{ config('services.newsletter.api_endpoint') }}"
20 | grant_type: password
21 | password: "{{ config('services.newsletter.password') }}"
22 | client_id: "{{ config('services.newsletter.client_id') }}"
23 |
--------------------------------------------------------------------------------
/src/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import routesMixin from './mixins/routes'
2 |
3 | require('./bootstrap')
4 |
5 | window.Vue = require('vue')
6 |
7 | Vue.component('health-panel', require('./components/Panel.vue').default)
8 |
9 | const app = new Vue({
10 | el: '#app',
11 |
12 | mixins: [routesMixin],
13 |
14 | data: {
15 | config: { loaded: false },
16 | },
17 |
18 | methods: {
19 | loadConfig() {
20 | let $this = this
21 |
22 | return axios.get($this.route('pragmarx.health.config')).then(function(response) {
23 | response.data.loaded = true
24 |
25 | $this.config = response.data
26 |
27 | $('.chart').css(
28 | 'height',
29 | $this.config.database.graphs.height + 'px',
30 | )
31 | })
32 | },
33 | },
34 |
35 | mounted() {
36 | this.loadConfig()
37 | },
38 | })
39 |
--------------------------------------------------------------------------------
/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/Data/Models/HealthCheck.php:
--------------------------------------------------------------------------------
1 | target->paths as $path) {
20 | if (! $this->getFilesystem()->isWritable($path)) {
21 | return $this->makeResult(
22 | false,
23 | sprintf($this->target->getErrorMessage(), $path)
24 | );
25 | }
26 | }
27 |
28 | return $this->makeHealthyResult();
29 | }
30 |
31 | public function getFilesystem()
32 | {
33 | if ($this->filesystem) {
34 | return $this->filesystem;
35 | }
36 |
37 | return $this->filesystem = app(Filesystem::class);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/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/Support/LocallyProtected.php:
--------------------------------------------------------------------------------
1 | header('API-Token');
19 |
20 | try {
21 | $cacheKey = decrypt($token);
22 | } catch (\Throwable $exception) {
23 | return false;
24 | }
25 |
26 | $value = Cache::get($cacheKey);
27 |
28 | return $value === $cacheKey;
29 | }
30 |
31 | public function protect($timeout)
32 | {
33 | $cacheKey = Constants::SERVER_VARS_CACHE_KEY_PREFIX.'-'.Str::random();
34 |
35 | Cache::put($cacheKey, $cacheKey, $timeout ?? 60);
36 |
37 | return encrypt($cacheKey);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Events/HealthPing.php:
--------------------------------------------------------------------------------
1 | callbackUrl = $callbackUrl;
33 |
34 | $this->channel = $channel;
35 |
36 | $this->target = $target;
37 | }
38 |
39 | /**
40 | * Get the channels the event should broadcast on.
41 | *
42 | * @return array
43 | */
44 | public function broadcastOn()
45 | {
46 | return [$this->channel];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Support/Traits/Routing.php:
--------------------------------------------------------------------------------
1 | isset($route['middleware']) ? $route['middleware'] : [],
15 | ];
16 |
17 | $this->getRouter()->group($attributes, function () use ($route, $name) {
18 | $action = isset($route['controller'])
19 | ? "{$route['controller']}@{$route['action']}"
20 | : $route['action'];
21 |
22 | $this->getRouter()->get($route['uri'], [
23 | 'as' => $name ?: $route['name'],
24 | 'uses' => $action,
25 | ]);
26 | });
27 | }
28 |
29 | /**
30 | * Get the current router.
31 | *
32 | * @return mixed
33 | */
34 | protected function getRouter()
35 | {
36 | return app()->router;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/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/SecurityChecker.php:
--------------------------------------------------------------------------------
1 | getCommand(), $output);
18 |
19 | $alerts = collect(json_decode(collect($output)->join(''), true))->keys();
20 |
21 | if (count($alerts) === 0) {
22 | return $this->makeHealthyResult();
23 | }
24 |
25 | $problems = collect($alerts)->implode(', ');
26 |
27 | return $this->makeResult(
28 | false,
29 | sprintf($this->target->getErrorMessage(), $problems)
30 | );
31 | }
32 |
33 | public function getCommand()
34 | {
35 | if (! file_exists($executable = $this->target->resource->executable)) {
36 | throw new Exception("The security checker executable was not found: $executable");
37 | }
38 |
39 | return $executable.' -format json '.base_path('composer.lock');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/PhpUnit/bootstrap.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/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/resources/views/default/partials/well.blade.php:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/resources/js/components/Chart.vue:
--------------------------------------------------------------------------------
1 |
50 |
--------------------------------------------------------------------------------
/src/database/migrations/2018_09_09_000001_create_table_health_checks.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 |
18 | $table->string('resource_name');
19 |
20 | $table->string('resource_slug')->index();
21 |
22 | $table->string('target_name');
23 |
24 | $table->string('target_slug')->index();
25 |
26 | $table->string('target_display');
27 |
28 | $table->boolean('healthy');
29 |
30 | $table->text('error_message')->nullable();
31 |
32 | $table->float('runtime');
33 |
34 | $table->string('value')->nullable();
35 |
36 | $table->string('value_human')->nullable();
37 |
38 | $table->timestamp('created_at', 0)->index();
39 | });
40 | }
41 |
42 | /**
43 | * Reverse the migrations.
44 | *
45 | * @return void
46 | */
47 | public function down()
48 | {
49 | Schema::drop('health_checks');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Checkers/ServerLoad.php:
--------------------------------------------------------------------------------
1 | getCurrentUptime();
17 |
18 | $inTrouble =
19 | $current['load_1'] > $this->target->maxLoad['load_1'] ||
20 | $current['load_5'] > $this->target->maxLoad['load_5'] ||
21 | $current['load_15'] > $this->target->maxLoad['load_15'];
22 |
23 | return $this->makeResult(! $inTrouble, $this->makeMessage($current));
24 | }
25 |
26 | protected function makeMessage($current, $saved = null)
27 | {
28 | $current['load_1'] > $this->target->maxLoad['load_1'] ||
29 | $current['load_5'] > $this->target->maxLoad['load_5'] ||
30 | $current['load_15'] > $this->target->maxLoad['load_15'];
31 |
32 | return sprintf(
33 | $this->target->getErrorMessage(),
34 | $current['load_1'],
35 | $current['load_5'],
36 | $current['load_15'],
37 | $this->target->maxLoad['load_1'],
38 | $this->target->maxLoad['load_5'],
39 | $this->target->maxLoad['load_15']
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/Checkers/Queue.php:
--------------------------------------------------------------------------------
1 | target->connection
19 | ?: app('config')['queue.default'];
20 |
21 | $queue = filled($this->target->queue ?? null)
22 | ? $this->target->queue
23 | : app('config')->get(
24 | "queue.connections.{$connection}.queue",
25 | 'default'
26 | );
27 |
28 | app('queue')->pushOn(
29 | $queue,
30 | instantiate($this->target->testJob)
31 | );
32 |
33 | $worker = app('queue.worker');
34 |
35 | $worker->setCache(instantiate($this->target->cacheInstance)->driver());
36 |
37 | $worker->runNextJob($connection, $queue, $this->gatherWorkerOptions());
38 |
39 | return $this->makeResult(true);
40 | }
41 |
42 | /**
43 | * Gather all of the queue worker options as a single object.
44 | *
45 | * @return \Illuminate\Queue\WorkerOptions
46 | */
47 | protected function gatherWorkerOptions()
48 | {
49 | return new WorkerOptions(0, 0, 0, 0, 0, false);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 |
2 | // Fonts
3 | @import url('https://fonts.googleapis.com/css?family=Ubuntu');
4 |
5 | // Variables
6 | @import 'variables';
7 |
8 | // Bootstrap
9 | @import '~bootstrap/scss/bootstrap';
10 |
11 | body {
12 | background-color: #FFFEEA;
13 | }
14 |
15 | .navbar-laravel {
16 | background-color: rgba(239, 204, 18, 0.14);
17 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
18 | }
19 |
20 | .title {
21 | font-size: 1.2em;
22 | font-weight: 100;
23 | margin-top: 1px;
24 | margin-bottom: 1px;
25 | position: relative;
26 | }
27 |
28 | .subtitle {
29 | line-height: 10px;
30 | font-size: 1em;
31 | color: blue;
32 | margin-bottom: 1px;
33 | }
34 |
35 | .nav-button {
36 | margin: 0 !important;
37 | }
38 |
39 | .target-card {
40 | border-color: #bdbdbd !important;
41 | background-color: white;
42 | }
43 |
44 | .shadow {
45 | box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
46 | }
47 |
48 | .color-neutral {
49 | color: blue;
50 | fill: blue;
51 | }
52 |
53 | .color-success {
54 | color: green;
55 | fill: green;
56 | }
57 |
58 | .color-danger {
59 | color: #FFA8C4;
60 | fill: red;
61 | }
62 |
63 | .color-neutral-background {
64 | }
65 |
66 | .color-success-background {
67 | }
68 |
69 | .color-danger-background {
70 | background-color: #FFE5C4;
71 | }
72 |
73 | .info-icon {
74 | margin-top: 7px;
75 | width: 1.8em;
76 | opacity: 0.5;
77 | }
78 |
79 | .chart {
80 | margin-top: 7px;
81 | margin-right: -50px;
82 | margin-left: -7px;
83 | margin-bottom: -7px;
84 | }
85 |
--------------------------------------------------------------------------------
/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/config/resources/Https.yml:
--------------------------------------------------------------------------------
1 | name: Https
2 | abbreviation: https
3 | checker: PragmaRX\Health\Checkers\Https
4 | notify: true
5 | column_size: 3
6 | timeout_message: "[TIMEOUT] A request to %s took %s seconds. Timeout is set to %s seconds."
7 | connection_timeout: 30
8 | roundtrip_timeout: 30
9 | targets:
10 | - default:
11 | urls:
12 | - '{{ config("app.url") }}'
13 | - https://google.com
14 | - https://yahoo.com:
15 | headers:
16 | Authorization: Basic zzz0czBmz2zkzXRpb25sbzVpzzZ1zXRzz2zuZzz6z29hzC1zZWzkzQ==
17 | - https://api.sendinblue.com/v3/account:
18 | headers:
19 | api-key: "{{ config('services.sendinblue.api_key') }}"
20 | method: GET
21 | - url-via-config-1:
22 | url: "{{ config('services.see_tickets.api.endpoint') }}"
23 | method: POST
24 | auth:
25 | - "{{ config('services.see_tickets.api.username') }}"
26 | - "{{ config('services.see_tickets.api.password') }}"
27 | - basic
28 | - url-via-config-2:
29 | url: "{{ config('services.whatever.url') }}"
30 | method: POST
31 | debug: false
32 | form_params:
33 | username: "{{ config('services.whatever.username') }}"
34 | resource: "{{ config('services.whatever.api_endpoint') }}"
35 | grant_type: password
36 | password: "{{ config('services.whatever.password') }}"
37 | client_id: "{{ config('services.whatever.client_id') }}"
38 |
--------------------------------------------------------------------------------
/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/config/resources/ServerVars.yml:
--------------------------------------------------------------------------------
1 | name: ServerVars
2 | abbreviation: server-vars
3 | checker: PragmaRX\Health\Checkers\ServerVars
4 | error_message: "These $_SERVER vars doesn't match the expected value: %s"
5 | notify: true
6 | column_size: 3
7 | targets:
8 | - default:
9 | config:
10 | route: pragmarx.health.server-vars
11 | cache_timeout: 5
12 | query_string: "query=testing-cdn-enabled-query-strings"
13 | auth:
14 | username: "{{ config('auth.http.user') }}"
15 | password: "{{ config('auth.http.password') }}"
16 | vars:
17 | server_port:
18 | name: SERVER_PORT
19 | operator: equals
20 | value: 443
21 | strict: false
22 | mandatory: true
23 | server_software:
24 | name: SERVER_SOFTWARE
25 | operator: contains
26 | value: "nginx/1.20"
27 | mandatory: true
28 | query_string:
29 | name: QUERY_STRING
30 | operator: contains
31 | value: "query=testing-cdn-enabled-query-strings"
32 | mandatory: true
33 | http_x_forwarded_proto:
34 | name: HTTP_X_FORWARDED_PROTO
35 | operator: equals
36 | value: https
37 | mandatory: false
38 | http_cloudfront_forwarded_proto:
39 | name: HTTP_CLOUDFRONT_FORWARDED_PROTO
40 | operator: equals
41 | value: https
42 | mandatory: false
43 | http_x_forwarded_port:
44 | name: HTTP_X_FORWARDED_PORT
45 | operator: equals
46 | value: 443
47 | strict: false
48 | mandatory: false
49 |
--------------------------------------------------------------------------------
/src/config/resources/Extensions.yml:
--------------------------------------------------------------------------------
1 | name: Extensions are installed
2 | abbreviation: extensions-installed
3 | checker: PragmaRX\Health\Checkers\Extensions
4 | notify: true
5 | error_message:
6 | "The following extensions are not installed or enabled: %s"
7 | column_size: 3
8 | targets:
9 | default:
10 | - items:
11 | - Core
12 | - phpdbg_webhelper
13 | - date
14 | - libxml
15 | - openssl
16 | - pcre
17 | - sqlite3
18 | - zlib
19 | - bcmath
20 | - bz2
21 | - calendar
22 | - ctype
23 | - curl
24 | - dba
25 | - dom
26 | - hash
27 | - FFI
28 | - fileinfo
29 | - filter
30 | - ftp
31 | - gd
32 | - gettext
33 | - gmp
34 | - SPL
35 | - iconv
36 | - intl
37 | - json
38 | - ldap
39 | - mbstring
40 | - session
41 | - standard
42 | - odbc
43 | - pcntl
44 | - mysqlnd
45 | - PDO
46 | - pdo_dblib
47 | - pdo_mysql
48 | - PDO_ODBC
49 | - pdo_pgsql
50 | - pdo_sqlite
51 | - pgsql
52 | - Phar
53 | - posix
54 | - pspell
55 | - readline
56 | - Reflection
57 | - mysqli
58 | - shmop
59 | - SimpleXML
60 | - soap
61 | - sockets
62 | - sodium
63 | - exif
64 | - sysvmsg
65 | - sysvsem
66 | - sysvshm
67 | - tidy
68 | - tokenizer
69 | - xml
70 | - xmlreader
71 | - xmlwriter
72 | - xsl
73 | - zip
74 | - redis
75 | - Zend OPcache
76 | - xdebug
77 |
--------------------------------------------------------------------------------
/src/Support/Timer.php:
--------------------------------------------------------------------------------
1 | start();
30 | }
31 |
32 | public static function stopSB()
33 | {
34 | $result = static::isStatic()
35 | ? SBTimer::stop()
36 | : static::getSBInstance()->stop();
37 |
38 | if (is_object($result)) {
39 | return $result->asSeconds();
40 | }
41 |
42 | return $result;
43 | }
44 |
45 | public static function isStatic()
46 | {
47 | return static::getStaticMethodNames()->contains('start');
48 | }
49 |
50 | public static function getSBInstance()
51 | {
52 | if (! empty(static::$sbTimer)) {
53 | return static::$sbTimer;
54 | }
55 |
56 | return static::$sbTimer = new SBTimer();
57 | }
58 |
59 | public static function getStaticMethodNames()
60 | {
61 | return collect((new \ReflectionClass(SBTimer::class))->getMethods(\ReflectionMethod::IS_STATIC))->map(function ($method) {
62 | return $method->name;
63 | });
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Listeners/NotifyHealthIssue.php:
--------------------------------------------------------------------------------
1 | map(
19 | function ($item) {
20 | $model = instantiate(
21 | config('health.notifications.users.model')
22 | );
23 |
24 | $model->email = $item;
25 |
26 | return $model;
27 | }
28 | );
29 | }
30 |
31 | /**
32 | * Handle the event.
33 | *
34 | * @param RaiseHealthIssue $event
35 | * @return void
36 | *
37 | * @throws ReflectionException
38 | */
39 | public function handle(RaiseHealthIssue $event)
40 | {
41 | $notifier = config('health.notifications.notifier');
42 | if ($notifier) {
43 | $notifierClass = new ReflectionClass($notifier);
44 | } else {
45 | $notifierClass = HealthStatus::class;
46 | }
47 | try {
48 | $event->failure->targets->each(function ($target) use ($event, $notifierClass) {
49 | if (! $target->result->healthy) {
50 | Notification::send(
51 | $this->getNotifiableUsers(),
52 | $notifierClass->newInstance($target, $event->channel)
53 | );
54 | }
55 | });
56 | } catch (\Exception $exception) {
57 | report($exception);
58 | } catch (\Throwable $error) {
59 | report($error);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/PhpUnit/TestCase.php:
--------------------------------------------------------------------------------
1 | set('health.resources.enabled', static::ALL_RESOURCES);
69 |
70 | return [HealthServiceProvider::class];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Support/Traits/ToArray.php:
--------------------------------------------------------------------------------
1 | ___toArray($subject, $maxDepth);
27 | }
28 |
29 | /**
30 | * Convert object to array.
31 | *
32 | * @param $subject
33 | * @return array|null
34 | */
35 | public function ___toArray($subject)
36 | {
37 | $callback = $this->__getToArrayCallBack();
38 |
39 | return $callback($subject);
40 | }
41 |
42 | /**
43 | * Generate a callback to transform object to array.
44 | *
45 | * @return \Closure
46 | */
47 | public function __getToArrayCallBack()
48 | {
49 | return function ($subject) {
50 | static::$__depth++;
51 |
52 | if ($subject instanceof Arrayable) {
53 | $subject = $subject->toArray();
54 | }
55 |
56 | if (is_object($subject)) {
57 | $subject = get_object_vars($subject);
58 | }
59 |
60 | if (is_array($subject)) {
61 | if (static::$__depth <= static::$__maxDepth) {
62 | $subject = array_map(
63 | $this->__getToArrayCallBack(),
64 | $subject
65 | );
66 | } else {
67 | $subject = null;
68 | }
69 | }
70 |
71 | static::$__depth--;
72 |
73 | return $subject;
74 | };
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Support/Result.php:
--------------------------------------------------------------------------------
1 | healthy = $healthy;
49 |
50 | $this->errorMessage = $errorMessage;
51 |
52 | // Currently the status is inferred from the $healthy flag until full support is added.
53 | $this->status = $healthy ? self::OK : self::CRITICAL;
54 | }
55 |
56 | /**
57 | * Value setter.
58 | *
59 | * @param mixed $value
60 | * @return Result
61 | */
62 | public function setValue($value)
63 | {
64 | $this->value = $value;
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * Value for humans setter.
71 | *
72 | * @param string $valueHuman
73 | * @return Result
74 | */
75 | public function setValueHuman(string $valueHuman)
76 | {
77 | $this->valueHuman = $valueHuman;
78 |
79 | return $this;
80 | }
81 |
82 | /**
83 | * Get the result's status of the check.
84 | *
85 | * @return string one of the consts e.g. result::OK
86 | */
87 | public function getStatus(): string
88 | {
89 | return $this->status;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Checkers/PortCheck.php:
--------------------------------------------------------------------------------
1 | portIsNotConnectable(
18 | $ipAddress = ip_address_from_hostname($this->target->hostname)
19 | )
20 | ) {
21 | return $this->makeFinalResult($ipAddress);
22 | }
23 |
24 | return $this->makeHealthyResult();
25 | }
26 |
27 | /**
28 | * Get hostname and IP.
29 | *
30 | * @param $hostname
31 | * @return mixed
32 | */
33 | protected function hosnameAndIp($hostname, $ipAdress)
34 | {
35 | return $hostname.($hostname != $ipAdress ? " ({$ipAdress})" : '');
36 | }
37 |
38 | /**
39 | * Make the result.
40 | *
41 | * @param bool $ipAddress
42 | * @return Result
43 | */
44 | protected function makeFinalResult($ipAddress)
45 | {
46 | return $this->target->setResult(
47 | $this->makeResult(
48 | false,
49 | sprintf(
50 | $this->target->getErrorMessage(),
51 | $this->hosnameAndIp($this->target->hostname, $ipAddress),
52 | $this->target->port
53 | )
54 | )
55 | )->getResult();
56 | }
57 |
58 | public function portCheck($ipAddress, $port, $timeout)
59 | {
60 | $fp = @fsockopen($ipAddress, $port, $errno, $errstr, $timeout);
61 |
62 | if (gettype($fp) !== 'resource') {
63 | return false;
64 | }
65 |
66 | fclose($fp);
67 |
68 | return true;
69 | }
70 |
71 | /**
72 | * @param $ipAddress
73 | * @return bool
74 | */
75 | protected function portIsNotConnectable($ipAddress)
76 | {
77 | return ! $this->portCheck(
78 | $ipAddress,
79 | $this->target->port,
80 | $this->target->timeout ?? 1
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Checkers/Mail.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/Support/Traits/Database.php:
--------------------------------------------------------------------------------
1 | database = $this->__load();
15 | }
16 |
17 | /**
18 | * Load cache.
19 | *
20 | * @return \Illuminate\Support\Collection
21 | */
22 | public function __load()
23 | {
24 | if (! file_exists($file = $this->getDatabaseFileName())) {
25 | return collect();
26 | }
27 |
28 | return collect(json_decode(file_get_contents($file), true));
29 | }
30 |
31 | /**
32 | * Get cache filename.
33 | *
34 | * @return string|null
35 | */
36 | protected function getDatabaseFileName()
37 | {
38 | return $this->target->saveTo;
39 | }
40 |
41 | /**
42 | * Check if database is enabled.
43 | *
44 | * @return bool
45 | */
46 | protected function databaseEnabled()
47 | {
48 | return config('health.database.enabled');
49 | }
50 |
51 | protected function saveResultsToDatabase($target, $result)
52 | {
53 | $model = config('health.database.model', HealthCheck::class);
54 |
55 | $model::create([
56 | 'resource_name' => $resource = $target->resource->name,
57 | 'resource_slug' => $target->resource->slug,
58 | 'target_name' => $target->name,
59 | 'target_slug' => Str::slug($target->name),
60 | 'target_display' => $target->display,
61 | 'healthy' => $result->healthy,
62 | 'error_message' => $result->errorMessage,
63 | 'runtime' => $result->elapsedTime ?? 0,
64 | 'value' => $result->value,
65 | 'value_human' => $result->valueHuman,
66 | ]);
67 |
68 | return $model::where([
69 | 'resource_slug' => $target->resource->slug,
70 | 'target_name' => $target->name,
71 | ])
72 | ->orderBy('created_at', 'desc')
73 | ->take(config('health.database.max_records'))
74 | ->get();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/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/Database.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/resources/dist/js/app.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.6.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5 | */
6 |
7 | /*!
8 | * Chart.js v2.9.4
9 | * https://www.chartjs.org
10 | * (c) 2020 Chart.js Contributors
11 | * Released under the MIT License
12 | */
13 |
14 | /*!
15 | * Sizzle CSS Selector Engine v2.3.6
16 | * https://sizzlejs.com/
17 | *
18 | * Copyright JS Foundation and other contributors
19 | * Released under the MIT license
20 | * https://js.foundation/
21 | *
22 | * Date: 2021-02-16
23 | */
24 |
25 | /*!
26 | * Vue.js v2.6.14
27 | * (c) 2014-2021 Evan You
28 | * Released under the MIT License.
29 | */
30 |
31 | /*!
32 | * jQuery JavaScript Library v3.6.0
33 | * https://jquery.com/
34 | *
35 | * Includes Sizzle.js
36 | * https://sizzlejs.com/
37 | *
38 | * Copyright OpenJS Foundation and other contributors
39 | * Released under the MIT license
40 | * https://jquery.org/license
41 | *
42 | * Date: 2021-03-02T17:08Z
43 | */
44 |
45 | /**
46 | * @license
47 | * Lodash
48 | * Copyright OpenJS Foundation and other contributors
49 | * Released under MIT license
50 | * Based on Underscore.js 1.8.3
51 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
52 | */
53 |
54 | /**!
55 | * @fileOverview Kickass library to create and place poppers near their reference elements.
56 | * @version 1.16.1
57 | * @license
58 | * Copyright (c) 2016 Federico Zivolo and contributors
59 | *
60 | * Permission is hereby granted, free of charge, to any person obtaining a copy
61 | * of this software and associated documentation files (the "Software"), to deal
62 | * in the Software without restriction, including without limitation the rights
63 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
64 | * copies of the Software, and to permit persons to whom the Software is
65 | * furnished to do so, subject to the following conditions:
66 | *
67 | * The above copyright notice and this permission notice shall be included in all
68 | * copies or substantial portions of the Software.
69 | *
70 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
71 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
72 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
73 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
74 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
75 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
76 | * SOFTWARE.
77 | */
78 |
79 | //! moment.js
80 |
81 | //! moment.js locale configuration
82 |
--------------------------------------------------------------------------------
/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/ServerVars.php:
--------------------------------------------------------------------------------
1 | requestServerVars();
23 |
24 | collect($this->target->config['vars'])->each(function ($var) {
25 | $this->checkVar($var);
26 | });
27 |
28 | return blank($this->errors)
29 | ? $this->makeHealthyResult()
30 | : $this->makeResult(false, sprintf($this->target->resource->errorMessage, implode('; ', $this->errors)));
31 | }
32 |
33 | public function requestServerVars()
34 | {
35 | $url = $this->makeUrl();
36 |
37 | $bearer = (new LocallyProtected())->protect($this->target->config['cache_timeout'] ?? 60);
38 |
39 | $guzze = new Guzzle($this->getAuthorization());
40 |
41 | $response = $guzze->request('GET', $url, [
42 | 'headers' => ['API-Token' => $bearer],
43 | ]);
44 |
45 | if (($code = $response->getStatusCode()) !== 200) {
46 | throw new \Exception("Request to {$url} returned a status code {$code}");
47 | }
48 |
49 | $this->response = json_decode((string) $response->getBody(), true);
50 | }
51 |
52 | public function checkVar($var)
53 | {
54 | if (blank($this->response[$var['name']] ?? null)) {
55 | if ($var['mandatory']) {
56 | $this->errors[] = "{$var['name']} is empty";
57 | }
58 |
59 | return;
60 | }
61 |
62 | $got = $this->response[$var['name']];
63 |
64 | $expected = $var['value'];
65 |
66 | if (! $this->compare($var, $expected, $got)) {
67 | $this->errors[] = "{$var['name']}: expected '{$expected}' but got '{$got}'";
68 | }
69 | }
70 |
71 | public function compare($var, $expected, $got)
72 | {
73 | $operator = $var['operator'] ?? 'equals';
74 |
75 | $strict = $var['strict'] ?? true;
76 |
77 | if ($operator === 'equals') {
78 | return $strict ? $expected === $got : $expected == $got;
79 | }
80 |
81 | if ($operator === 'contains') {
82 | return Str::contains($got, $expected);
83 | }
84 |
85 | throw new \Exception("Operator '$operator' is not supported.");
86 | }
87 |
88 | public function makeUrl()
89 | {
90 | $url = route($this->target->config['route']);
91 |
92 | if ($queryString = $this->target->config['query_string']) {
93 | $url .= "?$queryString";
94 | }
95 |
96 | return $url;
97 | }
98 |
99 | public function getAuthorization()
100 | {
101 | if (blank($auth = $this->target->config['auth'] ?? null)) {
102 | return [];
103 | }
104 |
105 | return ['auth' => [$auth['username'], $auth['password']]];
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/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/Support/Cache.php:
--------------------------------------------------------------------------------
1 | getSeconds() !== false;
22 | }
23 |
24 | /**
25 | * Flush cache.
26 | *
27 | * @param bool $force
28 | * @param null $key
29 | */
30 | public function flush($force = false, $key = null)
31 | {
32 | if (! isset($this->flushed[$key]) && ($force || $this->needsToFlush())) {
33 | try {
34 | $this->forceFlush($key);
35 | } catch (Exception $exception) {
36 | report($exception);
37 | }
38 |
39 | $this->flushed[$key] = true;
40 | }
41 | }
42 |
43 | /**
44 | * Force cache flush.
45 | *
46 | * @param string|null $key
47 | */
48 | protected function forceFlush($key = null)
49 | {
50 | IlluminateCache::forget($key ?? config('health.cache.key'));
51 | }
52 |
53 | /**
54 | * Get cache seconds.
55 | *
56 | * @return mixed
57 | */
58 | public function getSeconds()
59 | {
60 | return config('health.cache.seconds');
61 | }
62 |
63 | /**
64 | * Get the request.
65 | *
66 | * @return \Illuminate\Foundation\Application|mixed
67 | */
68 | protected function getCurrentRequest()
69 | {
70 | return instantiate(Request::class);
71 | }
72 |
73 | /**
74 | * Get cached resources.
75 | *
76 | * @return \Illuminate\Support\Collection
77 | */
78 | public function getCachedResources()
79 | {
80 | $this->flush();
81 |
82 | return $this->cacheIsEnabled()
83 | ? IlluminateCache::get(config('health.cache.key'), collect())
84 | : collect();
85 | }
86 |
87 | /**
88 | * Check if cache needs to be flushed.
89 | *
90 | * @return bool
91 | */
92 | protected function needsToFlush()
93 | {
94 | return
95 | $this->cacheIsEnabled() && $this->getCurrentRequest()->get('flush');
96 | }
97 |
98 | /**
99 | * Cache all resources.
100 | *
101 | * @param Collection $resources
102 | * @return Collection
103 | */
104 | public function cacheResources($resources)
105 | {
106 | if ($this->cacheIsEnabled()) {
107 | IlluminateCache::put(
108 | config('health.cache.key'),
109 | $resources,
110 | $this->getSeconds()
111 | );
112 | }
113 |
114 | return $resources;
115 | }
116 |
117 | /**
118 | * Get an item from the cache, or store the default value.
119 | *
120 | * @param string $key
121 | * @param \Closure $callback
122 | * @return mixed
123 | */
124 | public function remember($key, \Closure $callback)
125 | {
126 | if (! $this->cacheIsEnabled()) {
127 | return $callback();
128 | }
129 |
130 | $key = config('health.cache.key').'|resource:'.$key;
131 |
132 | $this->flush(false, $key);
133 |
134 | return IlluminateCache::remember($key, $this->getSeconds(), $callback);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/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/Process.php:
--------------------------------------------------------------------------------
1 | checkMinMax($this->getProcessesRunningCount());
22 |
23 | if (! empty($message)) {
24 | return $this->makeResult(false, $message);
25 | }
26 |
27 | return $this->makeHealthyResult();
28 | }
29 |
30 | private function checkMinMax($processes)
31 | {
32 | return $this->buildMessage('minimum', $processes)
33 | ?: $this->buildMessage('maximum', $processes);
34 | }
35 |
36 | private function buildMessage($type, $processes)
37 | {
38 | $instances = $this->target->instances;
39 |
40 | if (! $count = (int) $instances[$type]['count']) {
41 | return '';
42 | }
43 |
44 | if ($type == 'minimum') {
45 | $diff = $processes - $count;
46 | } else {
47 | $diff = $count - $processes;
48 | }
49 |
50 | if ($diff < 0) {
51 | return sprintf(
52 | $instances[$type]['message'],
53 | $this->target->processName,
54 | $processes,
55 | $count
56 | );
57 | }
58 | }
59 |
60 | private function checkPidFile()
61 | {
62 | return $this->processPidFileIsLocked() ? 1 : 0;
63 | }
64 |
65 | private function countRunningProcesses()
66 | {
67 | if ($command = $this->getCommand()) {
68 | exec($command, $count);
69 |
70 | return count($count);
71 | }
72 |
73 | return 0;
74 | }
75 |
76 | /**
77 | * @param $file
78 | * @return bool
79 | */
80 | private function checkPidFileExistence($file)
81 | {
82 | if (file_exists($file)) {
83 | return 1;
84 | }
85 |
86 | throw new DomainException(
87 | sprintf($this->target->pidFileMissingErrorMessage, $file)
88 | );
89 | }
90 |
91 | /**
92 | * @param $file
93 | * @return bool
94 | */
95 | private function checkPidFileLockState($file)
96 | {
97 | try {
98 | $locked = flock($filePointer = fopen($file, 'r+'), LOCK_EX);
99 |
100 | flock($filePointer, LOCK_UN);
101 |
102 | fclose($filePointer);
103 |
104 | if (! $locked) {
105 | throw new DomainException(
106 | sprintf($this->target->pid_file_missing_not_locked, $file)
107 | );
108 | }
109 | } catch (\Exception $exception) {
110 | report($exception);
111 | }
112 | }
113 |
114 | /**
115 | * @return bool
116 | */
117 | private function processPidFileIsLocked()
118 | {
119 | $file = $this->target->pidFile;
120 |
121 | $this->checkPidFileExistence($file);
122 |
123 | $this->checkPidFileLockState($file);
124 |
125 | return 1;
126 | }
127 |
128 | private function getCommand()
129 | {
130 | $command = $this->target->command;
131 |
132 | $name = $this->target->processName;
133 |
134 | if ($command && $name) {
135 | return sprintf($command, $name);
136 | }
137 | }
138 |
139 | private function getProcessesRunningCount()
140 | {
141 | if ($this->target->method == static::METHOD_PROCESS_COUNT) {
142 | return $this->countRunningProcesses();
143 | }
144 |
145 | if ($this->target->method == static::METHOD_PID_FILE) {
146 | return $this->checkPidFile();
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/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/Support/helpers.php:
--------------------------------------------------------------------------------
1 | newInstanceArgs((array) $parameters);
19 | }
20 |
21 | return app($abstract);
22 | }
23 | }
24 |
25 | if (! function_exists('package_dir')) {
26 | /**
27 | * Get the package root directory.
28 | *
29 | * @return string
30 | */
31 | function package_dir()
32 | {
33 | $reflector = new ReflectionClass(Service::class);
34 |
35 | return dirname($reflector->getFileName());
36 | }
37 | }
38 |
39 | if (! function_exists('package_resources_dir')) {
40 | /**
41 | * Get package resources directory.
42 | *
43 | * @return string
44 | */
45 | function package_resources_dir()
46 | {
47 | return
48 | package_dir().
49 | DIRECTORY_SEPARATOR.
50 | 'config'.
51 | DIRECTORY_SEPARATOR.
52 | 'resources';
53 | }
54 | }
55 |
56 | if (! function_exists('is_absolute_path')) {
57 | /**
58 | * Check if string is absolute path.
59 | *
60 | * @param $path
61 | * @return string
62 | */
63 | function is_absolute_path($path)
64 | {
65 | // Optional wrapper(s).
66 | $regExp =
67 | // Optional root prefix.
68 | '%^(?(?:[[:print:]]{2,}://)*)'.
69 | '(?(?:[[:alpha:]]:/|/)?)'.
70 | // Actual path.
71 | '(?(?:[[:print:]]*))$%';
72 |
73 | $parts = [];
74 |
75 | preg_match($regExp, $path, $parts);
76 |
77 | if ('' !== $parts['root']) {
78 | return true;
79 | }
80 |
81 | return false;
82 | }
83 | }
84 |
85 | if (! function_exists('bytes_to_human')) {
86 | /**
87 | * Convert bytes to human readable.
88 | *
89 | * @return string
90 | */
91 | function bytes_to_human($bytes)
92 | {
93 | $base = log($bytes) / log(1024);
94 |
95 | $suffix = ['', 'KB', 'MB', 'GB', 'TB'];
96 |
97 | $f_base = floor($base);
98 |
99 | return round(pow(1024, $base - floor($base)), 1).$suffix[$f_base];
100 | }
101 | }
102 |
103 | if (! function_exists('human_to_bytes')) {
104 | /**
105 | * Convert bytes to human readable.
106 | *
107 | * @return string
108 | */
109 | function human_to_bytes($str)
110 | {
111 | $str = trim($str);
112 |
113 | $num = (float) $str;
114 |
115 | if (strtoupper(substr($str, -1)) == 'B') {
116 | $str = substr($str, 0, -1);
117 | }
118 |
119 | switch (strtoupper(substr($str, -1))) {
120 | case 'P':
121 | $num *= 1024;
122 | case 'T':
123 | $num *= 1024;
124 | case 'G':
125 | $num *= 1024;
126 | case 'M':
127 | $num *= 1024;
128 | case 'K':
129 | $num *= 1024;
130 | }
131 |
132 | return $num;
133 | }
134 | }
135 |
136 | if (! function_exists('ip_address_from_hostname')) {
137 | function ip_address_from_hostname($host)
138 | {
139 | if (
140 | filter_var(
141 | $host,
142 | FILTER_VALIDATE_IP,
143 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
144 | )
145 | ) {
146 | return $host;
147 | }
148 |
149 | if (filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
150 | return gethostbyname($host);
151 | }
152 |
153 | return false;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/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/ServerUptime.php:
--------------------------------------------------------------------------------
1 | loadDatabase();
23 |
24 | $current = $this->getCurrentUptime();
25 |
26 | $this->persist($current);
27 |
28 | $rebooted =
29 | ($cs = $this->uptimeInSeconds($current)) <
30 | ($ss = $this->uptimeInSeconds($this->database));
31 |
32 | return $this->makeResult(
33 | ! $rebooted,
34 | $this->makeMessage($current, $this->database)
35 | );
36 | }
37 |
38 | /**
39 | * Execute command to get an uptime string.
40 | *
41 | * @return mixed|string
42 | *
43 | * @throws DomainException
44 | */
45 | private function executeUptimeCommand()
46 | {
47 | $error = exec($this->target->command, $system_string, $output);
48 |
49 | if ($output !== 0) {
50 | throw new DomainException((string) $error);
51 | }
52 |
53 | return (! is_array($system_string) || empty($system_string))
54 | ? ''
55 | : $system_string[0];
56 | }
57 |
58 | /**
59 | * Get current uptime.
60 | *
61 | * @return array
62 | *
63 | * @throws DomainException
64 | */
65 | protected function getCurrentUptime()
66 | {
67 | return $this->parseUptimeString($this->executeUptimeCommand());
68 | }
69 |
70 | /**
71 | * Normalize uptime matches.
72 | *
73 | * @param $matches
74 | * @return \Illuminate\Support\Collection
75 | */
76 | protected function normalizeMatches($matches)
77 | {
78 | return collect($matches)
79 | ->filter(function ($item, $key) {
80 | return ! is_numeric($key);
81 | })
82 | ->map(function ($item, $key) {
83 | $return = $item[0];
84 |
85 | if (Str::startsWith($key, 'load')) {
86 | $return = floatval($return);
87 | } elseif (is_numeric($return)) {
88 | $return = (int) $return;
89 | } elseif (empty($return)) {
90 | $return = null;
91 | }
92 |
93 | return $return;
94 | })
95 | ->toArray();
96 | }
97 |
98 | /**
99 | * Parse the uptime string.
100 | *
101 | * @param $system_string
102 | * @return array
103 | */
104 | protected function parseUptimeString($system_string)
105 | {
106 | $matches = [];
107 |
108 | preg_match(
109 | $this->target->regex,
110 | $system_string,
111 | $matches,
112 | PREG_OFFSET_CAPTURE
113 | );
114 |
115 | $matches = $this->normalizeMatches($matches);
116 |
117 | $matches['uptime_string'] = $system_string;
118 |
119 | return $matches;
120 | }
121 |
122 | /**
123 | * Convert uptime to seconds.
124 | *
125 | * @param $date
126 | * @return int
127 | */
128 | protected function uptimeInSeconds($date)
129 | {
130 | return
131 | (isset($date['up_days']) ? $date['up_days'] * 24 * 60 : 0) +
132 | (isset($date['up_hours']) ? $date['up_hours'] * 60 : 0) +
133 | (isset($date['up_minutes']) ? $date['up_minutes'] : 0);
134 | }
135 |
136 | /**
137 | * Make uptime message.
138 | *
139 | * @param $current
140 | * @param $saved
141 | * @return string
142 | */
143 | protected function makeMessage($current, $saved = null)
144 | {
145 | $current = $this->toUptimeString($current);
146 |
147 | $saved = $this->toUptimeString($saved);
148 |
149 | return sprintf($this->target->getErrorMessage(), $current, $saved);
150 | }
151 |
152 | /**
153 | * Convert uptime to human readable string.
154 | *
155 | * @param $uptime
156 | * @return string
157 | */
158 | public function toUptimeString($uptime)
159 | {
160 | return (string) CarbonInterval::days(
161 | isset($uptime['up_days']) ? $uptime['up_days'] : 0
162 | )
163 | ->hours(isset($uptime['up_hours']) ? $uptime['up_hours'] : 0)
164 | ->minutes(isset($uptime['up_minutes']) ? $uptime['up_minutes'] : 0);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Service.php:
--------------------------------------------------------------------------------
1 | resourceChecker = $resourceChecker;
30 |
31 | $this->cache = $cache;
32 | }
33 |
34 | /**
35 | * Check Resources.
36 | *
37 | * @param bool $force
38 | * @return \Illuminate\Support\Collection
39 | *
40 | * @throws \Exception
41 | */
42 | public function checkResources($force = false)
43 | {
44 | return $this->resourceChecker->checkResources($force);
45 | }
46 |
47 | /**
48 | * Check one resource.
49 | *
50 | * @param $slug
51 | * @return array
52 | *
53 | * @throws \Exception
54 | */
55 | public function checkResource($slug)
56 | {
57 | return $this->resourceChecker->checkResource($slug);
58 | }
59 |
60 | /**
61 | * Get services health.
62 | *
63 | * @return mixed
64 | *
65 | * @throws \Exception
66 | */
67 | public function health()
68 | {
69 | return $this->checkResources();
70 | }
71 |
72 | /**
73 | * Get resources.
74 | *
75 | * @return mixed
76 | *
77 | * @throws \Exception
78 | */
79 | public function getResources()
80 | {
81 | return $this->resourceChecker->getResources();
82 | }
83 |
84 | /**
85 | * Get a silent checker and notifier closure.
86 | *
87 | * @return \Closure
88 | */
89 | public function getSilentChecker()
90 | {
91 | return function () {
92 | return $this->checkResources();
93 | };
94 | }
95 |
96 | /**
97 | * Make a string result of all resources.
98 | *
99 | * @param $string
100 | * @param string Result::status $resultStatus
101 | * @return string
102 | */
103 | private function makeString($string, $resultStatus)
104 | {
105 | // To preserve current ok/fail behaviour, it will override the result
106 | // status string with 'fail', when the status is critical, and if the
107 | // fail string was set.
108 | if (
109 | $resultStatus === Result::CRITICAL
110 | && is_null(config('health.string.'.strtolower($resultStatus)))
111 | && null !== config('health.string.fail')
112 | ) {
113 | $resultStatus = 'fail';
114 | }
115 | $resultStatusOutput = config('health.string.'.strtolower($resultStatus));
116 |
117 | // If not defined, it should use the default string for the status.
118 | return $string.($resultStatusOutput ?? $resultStatus);
119 | }
120 |
121 | /**
122 | * Check and get a resource.
123 | *
124 | * @param $name
125 | * @return mixed
126 | *
127 | * @throws \Exception
128 | */
129 | public function resource($slug)
130 | {
131 | return $this->checkResource($slug);
132 | }
133 |
134 | /**
135 | * Set the action.
136 | *
137 | * @param $action
138 | */
139 | public function setAction($action)
140 | {
141 | $this->resourceChecker->setCurrentAction($action);
142 | }
143 |
144 | /**
145 | * @return mixed
146 | *
147 | * @throws \Exception
148 | */
149 | public function string(?string $filters = '')
150 | {
151 | // If filters are required, return "" for results that should not be included.
152 | if (! empty($filters)) {
153 | $filters = explode(',', strtolower($filters));
154 | }
155 |
156 | return collect($this->health())->reduce(function ($current, $resource) use ($filters) {
157 | $resourceStatus = $resource->getStatus();
158 |
159 | if (! empty($filters) && ! in_array(strtolower($resourceStatus), $filters)) {
160 | return $current;
161 | }
162 |
163 | return
164 | $current.
165 | ($current ? config('health.string.glue') : '').
166 | $this->makeString(
167 | $resource->abbreviation,
168 | $resourceStatus
169 | );
170 | });
171 | }
172 |
173 | /**
174 | * Set the caller.
175 | *
176 | * @param string $caller
177 | */
178 | public function setCaller($caller)
179 | {
180 | $this->resourceChecker->setCurrentCaller($caller);
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/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/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(?:=|<)(?