├── tests
├── Unit
│ └── .gitkeep
├── Browser
│ ├── screenshots
│ │ └── .gitignore
│ └── Pages
│ │ ├── Page.php
│ │ └── HomePage.php
├── CreatesApplication.php
├── bootstrap.php
├── UuidTest.php
├── DuskTestCase.php
├── ApiTest.php
├── TestCase.php
├── BrowserKitTest.php
├── DatabaseSetup.php
└── Feature
│ └── AutoCreateAdminTest.php
├── database
├── .gitignore
├── seeders
│ ├── DatabaseSeeder.php
│ └── TestDataSeeder.php
├── factories
│ ├── TeamFactory.php
│ ├── PingFactory.php
│ ├── UserFactory.php
│ ├── TemplateFactory.php
│ └── CronjobFactory.php
└── migrations
│ ├── 2016_11_28_085108_create_teams_table.php
│ ├── 2016_11_21_113124_create_pings_table.php
│ ├── 2016_12_08_085236_add_data_field_to_pings.php
│ ├── 2016_12_02_132330_add_notes_to_cronjobs.php
│ ├── 2019_08_12_135332_add_api_key_field_to_users_table.php
│ ├── 2016_11_28_102238_add_team_id_to_cronjobs_table.php
│ ├── 2016_12_12_084446_add_is_logging_to_cronjobs_table.php
│ ├── 2018_12_19_130752_add_cron_schedule_to_cronjobs.php
│ ├── 2016_11_18_134052_add_is_silenced_flag_to_users_table.php
│ ├── 2020_10_14_103632_add_uuid_to_failed_jobs_table.php
│ ├── 2018_01_05_102612_add_fallback_email_to_cronjobs_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2016_11_22_142917_add_last_alerted_to_cronjobs_table.php
│ ├── 2016_11_28_085241_create_team_user_table.php
│ ├── 2019_07_02_150701_create_failed_jobs_table.php
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2016_11_17_114550_create_cronjobs_table.php
│ └── 2020_03_26_101351_create_templates_table.php
├── resources
├── views
│ ├── vendor
│ │ └── .gitkeep
│ ├── template
│ │ ├── partials
│ │ │ └── index.blade.php
│ │ ├── index.blade.php
│ │ ├── create.blade.php
│ │ └── edit.blade.php
│ ├── job
│ │ ├── partials
│ │ │ ├── index.blade.php
│ │ │ └── status.blade.php
│ │ ├── admin
│ │ │ └── index.blade.php
│ │ ├── create.blade.php
│ │ ├── index.blade.php
│ │ └── edit.blade.php
│ ├── team
│ │ ├── partials
│ │ │ ├── form.blade.php
│ │ │ └── index.blade.php
│ │ ├── create.blade.php
│ │ ├── edit.blade.php
│ │ ├── index.blade.php
│ │ ├── show.blade.php
│ │ └── member
│ │ │ └── edit.blade.php
│ ├── partials
│ │ ├── errors.blade.php
│ │ └── navbar.blade.php
│ ├── home.blade.php
│ ├── profile
│ │ ├── edit.blade.php
│ │ └── show.blade.php
│ ├── user
│ │ ├── create.blade.php
│ │ ├── edit.blade.php
│ │ ├── show.blade.php
│ │ ├── partials
│ │ │ ├── profile.blade.php
│ │ │ └── form.blade.php
│ │ └── index.blade.php
│ ├── auth
│ │ ├── passwords
│ │ │ ├── email.blade.php
│ │ │ └── reset.blade.php
│ │ └── login.blade.php
│ ├── layouts
│ │ └── app.blade.php
│ ├── errors
│ │ └── 503.blade.php
│ └── welcome.blade.php
├── js
│ ├── components
│ │ ├── ApiKeyToggle.vue
│ │ └── JobTabs.vue
│ └── app.js
├── assets
│ ├── js
│ │ ├── app.js
│ │ └── components
│ │ │ └── JobTabs.vue
│ └── css
│ │ └── cronmon.css
├── lang
│ └── en
│ │ ├── pagination.php
│ │ ├── auth.php
│ │ └── passwords.php
└── css
│ └── cronmon.css
├── bootstrap
├── cache
│ └── .gitignore
├── autoload.php
└── app.php
├── storage
├── logs
│ └── .gitignore
├── app
│ ├── public
│ │ └── .gitignore
│ └── .gitignore
├── framework
│ ├── cache
│ │ └── .gitignore
│ ├── views
│ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ └── .gitignore
└── cronmon.png
├── public
├── robots.txt
├── favicon.ico
├── vendor
│ └── horizon
│ │ ├── img
│ │ ├── favicon.png
│ │ └── horizon.svg
│ │ └── mix-manifest.json
├── mix-manifest.json
├── .htaccess
├── web.config
└── index.php
├── .gitattributes
├── dt
├── docker
├── uploads.ini
├── vhost.conf
├── opcache.ini
├── custom_php.ini
├── ldap.conf
├── Dockerfile
├── app-healthcheck
├── start.sh
└── app-start
├── .dockerignore
├── .gitignore
├── .travis.yml
├── app
├── Models
│ ├── Ping.php
│ ├── CronUuid.php
│ └── Team.php
├── Http
│ ├── Controllers
│ │ ├── HomeController.php
│ │ ├── Controller.php
│ │ ├── ApiController.php
│ │ ├── Api
│ │ │ ├── TemplateController.php
│ │ │ └── CronjobController.php
│ │ ├── TeamMemberController.php
│ │ ├── Auth
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── ResetPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ └── RegisterController.php
│ │ ├── ProfileController.php
│ │ ├── TemplateController.php
│ │ ├── TeamController.php
│ │ ├── CronjobController.php
│ │ └── UserController.php
│ ├── Middleware
│ │ ├── EncryptCookies.php
│ │ ├── VerifyCsrfToken.php
│ │ ├── AdminOnly.php
│ │ ├── TrustProxies.php
│ │ └── RedirectIfAuthenticated.php
│ ├── Livewire
│ │ └── TemplateList.php
│ ├── Requests
│ │ ├── StoreCronjob.php
│ │ ├── StoreTemplate.php
│ │ ├── UpdateTemplate.php
│ │ └── UpdateCronjob.php
│ └── Kernel.php
├── Providers
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ ├── HorizonServiceProvider.php
│ ├── AppServiceProvider.php
│ ├── RouteServiceProvider.php
│ └── AuthServiceProvider.php
├── Console
│ ├── Commands
│ │ ├── SilenceAlerts.php
│ │ ├── UnsilenceAlerts.php
│ │ ├── CheckJobs.php
│ │ ├── TruncatePings.php
│ │ ├── CreateAdmin.php
│ │ ├── ReformatRoutes.php
│ │ ├── CronmonDiscover.php
│ │ └── AutoCreateAdmin.php
│ └── Kernel.php
├── Rules
│ └── ValidCronExpression.php
├── Notifications
│ └── JobHasGoneAwol.php
├── Exceptions
│ └── Handler.php
└── Policies
│ └── TemplatePolicy.php
├── phpunit.Dockerfile
├── webpack.mix.js
├── config
├── cronmon.php
├── hashing.php
├── compile.php
├── view.php
├── services.php
├── broadcasting.php
├── filesystems.php
├── livewire.php
├── queue.php
└── logging.php
├── .env.github
├── .env.travis
├── .env.gitlab
├── routes
├── console.php
└── api.php
├── server.php
├── .env.dusk.local
├── gulpfile.js
├── .env.example
├── .env.qa
├── package.json
├── phpunit-compose.yml
├── LICENSE
├── phpunit.github.xml
├── phpunit.xml
├── docker-compose-prod.yml
├── artisan
├── composer.json
├── docker-compose.yml
└── docker-compose-demo.yml
/tests/Unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 |
--------------------------------------------------------------------------------
/resources/views/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/resources/views/template/partials/index.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/Browser/screenshots/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohnotnow/cronmon/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/resources/views/job/partials/index.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/cronmon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohnotnow/cronmon/HEAD/storage/cronmon.png
--------------------------------------------------------------------------------
/dt:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker-compose -f phpunit-compose.yml up --build --exit-code-from phpunit
4 |
5 |
--------------------------------------------------------------------------------
/public/vendor/horizon/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohnotnow/cronmon/HEAD/public/vendor/horizon/img/favicon.png
--------------------------------------------------------------------------------
/docker/uploads.ini:
--------------------------------------------------------------------------------
1 | file_uploads = On
2 |
3 | memory_limit = 1024M
4 | upload_max_filesize = 64M
5 | post_max_size = 64M
6 | max_execution_time = 600
7 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git/
2 | node_modules/
3 | vendor/
4 | npm-debug
5 | Dockerfile
6 | stack.yml
7 | public/js
8 | public/css
9 | **/mix-manifest.json
10 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | config.php
2 | routes.php
3 | schedule-*
4 | compiled.php
5 | services.json
6 | events.scanned.php
7 | routes.scanned.php
8 | down
9 |
--------------------------------------------------------------------------------
/public/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/js/app.js": "/js/app.js",
3 | "/css/animate.css": "/css/animate.css",
4 | "/css/cronmon.css": "/css/cronmon.css"
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/storage
3 | /storage/*.key
4 | /vendor
5 | /.idea
6 | Homestead.json
7 | Homestead.yaml
8 | .env
9 | .phpunit.result.cache
10 |
11 |
--------------------------------------------------------------------------------
/resources/views/template/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 | @livewire('template-list')
7 |
8 |
9 | @endsection
--------------------------------------------------------------------------------
/resources/views/team/partials/form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/vendor/horizon/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=b11bd1f0cc14854a97de",
3 | "/app.css": "/app.css?id=9ce01eaaba790566b895",
4 | "/app-dark.css": "/app-dark.css?id=821c845f9bf3b7853c33"
5 | }
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 |
4 | language: php
5 |
6 | php:
7 | - '7.4'
8 |
9 | before_script:
10 | - composer self-update
11 | - cp .env.travis .env
12 | - composer install --no-interaction
13 | - phpenv rehash
14 |
15 | script:
16 | - vendor/bin/phpunit
17 |
--------------------------------------------------------------------------------
/app/Models/Ping.php:
--------------------------------------------------------------------------------
1 | 0)
2 |
3 |
4 | @foreach ($errors->all() as $error)
5 | - {{ $error }}
6 | @endforeach
7 |
8 |
9 | @endif
--------------------------------------------------------------------------------
/phpunit.Dockerfile:
--------------------------------------------------------------------------------
1 | ### PHP version we are targetting
2 | ARG PHP_VERSION=7.2
3 |
4 | FROM uogsoe/soe-php-apache:${PHP_VERSION} as prod
5 |
6 | WORKDIR /var/www/html
7 |
8 | USER nobody
9 |
10 | ENV APP_ENV=testing
11 | ENV APP_DEBUG=1
12 |
13 | CMD ["./vendor/bin/phpunit", "--testdox", "--stop-on-defect"]
14 |
15 |
--------------------------------------------------------------------------------
/docker/vhost.conf:
--------------------------------------------------------------------------------
1 |
2 | DocumentRoot /var/www/html/public
3 |
4 |
5 | AllowOverride all
6 | Require all granted
7 |
8 |
9 | ErrorLog ${APACHE_LOG_DIR}/error.log
10 | CustomLog ${APACHE_LOG_DIR}/access.log combined
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/Http/Controllers/HomeController.php:
--------------------------------------------------------------------------------
1 | user()->getAvailableJobs();
12 |
13 | return view('home', compact('jobs'));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | let mix = require("laravel-mix");
2 | let tailwind = require("tailwindcss");
3 | require("laravel-mix-purgecss");
4 |
5 | mix
6 | .js("resources/js/app.js", "public/js")
7 | .postCss("resources/css/animate.css", "public/css")
8 | .postCss("resources/css/cronmon.css", "public/css", [
9 | tailwind("tailwind.js")
10 | ]);
11 | // .purgeCss();
12 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Models/CronUuid.php:
--------------------------------------------------------------------------------
1 | toString();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EncryptCookies.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/job/partials/status.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($job->isAwol())
3 | @if ($job->is_silenced)
4 |
5 | @else
6 |
7 | @endif
8 | @else
9 |
10 | @endif
11 |
12 |
--------------------------------------------------------------------------------
/tests/Browser/Pages/Page.php:
--------------------------------------------------------------------------------
1 | '#selector',
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/resources/views/job/admin/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | All jobs
8 |
9 |
10 |
11 |
12 | @endsection
13 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
18 |
19 | return $app;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ApiController.php:
--------------------------------------------------------------------------------
1 | json(['errors' => 'Job not found', 'status' => 404], 404);
15 | }
16 | $job->ping(request('data', null));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/resources/assets/js/app.js:
--------------------------------------------------------------------------------
1 | window.Vue = require("vue");
2 |
3 | /**
4 | * Next, we will create a fresh Vue application instance and attach it to
5 | * the page. Then, you may begin adding components to this application
6 | * or customize the JavaScript scaffolding to fit your unique needs.
7 | */
8 |
9 | Vue.component("job-list", require("./components/JobList.vue"));
10 | Vue.component("job-tabs", require("./components/JobTabs.vue"));
11 |
12 | const app = new Vue({
13 | el: "#app"
14 | });
15 |
--------------------------------------------------------------------------------
/database/seeders/TestDataSeeder.php:
--------------------------------------------------------------------------------
1 | 'admin',
17 | 'password' => bcrypt('secret'),
18 | 'email' => 'admin@example.com',
19 | 'is_admin' => true,
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Middleware/AdminOnly.php:
--------------------------------------------------------------------------------
1 | user()->is_admin) {
19 | return redirect('/');
20 | }
21 |
22 | return $next($request);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/config/cronmon.php:
--------------------------------------------------------------------------------
1 | '[CRONMON]',
5 | 'alert_interval' => 60,
6 | 'keep_pings' => 100,
7 | 'fallback_delay' => 24, // hours
8 | 'admin_username' => env('CRONMON_ADMIN_USERNAME'),
9 | 'admin_username_file' => env('CRONMON_ADMIN_USERNAME_FILE'),
10 | 'admin_email' => env('CRONMON_ADMIN_EMAIL'),
11 | 'admin_email_file' => env('CRONMON_ADMIN_EMAIL_FILE'),
12 | 'admin_password' => env('CRONMON_ADMIN_PASSWORD'),
13 | 'admin_password_file' => env('CRONMON_ADMIN_PASSWORD_FILE'),
14 | ];
15 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrustProxies.php:
--------------------------------------------------------------------------------
1 | firstOrFail();
14 |
15 | $job = $template->createNewJob();
16 |
17 | return response()->json([
18 | 'data' => $job->toArray(),
19 | ]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | window.Vue = require("vue");
2 |
3 | /**
4 | * Next, we will create a fresh Vue application instance and attach it to
5 | * the page. Then, you may begin adding components to this application
6 | * or customize the JavaScript scaffolding to fit your unique needs.
7 | */
8 |
9 | Vue.component("job-list", require("./components/JobList.vue").default);
10 | Vue.component("job-tabs", require("./components/JobTabs.vue").default);
11 | Vue.component("api-key-toggle", require("./components/ApiKeyToggle.vue").default);
12 | const app = new Vue({
13 | el: "#app"
14 | });
15 |
16 |
--------------------------------------------------------------------------------
/.env.github:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=testing
3 | APP_KEY=base64:Q2CI9s88ePYqdrGvLc/q+r524KYMO6ON7C3Ujkn/OBw=
4 | APP_DEBUG=true
5 | APP_LOG_LEVEL=debug
6 | APP_URL=http://localhost
7 |
8 | LOG_CHANNEL=stack
9 |
10 | DB_CONNECTION=mysql
11 | DB_HOST=127.0.0.1
12 | DB_PORT=33306
13 | DB_DATABASE=homestead
14 | DB_USERNAME=root
15 | DB_PASSWORD=homestead
16 |
17 | BROADCAST_DRIVER=log
18 | CACHE_DRIVER=file
19 | SESSION_DRIVER=file
20 | SESSION_LIFETIME=120
21 | QUEUE_CONNECTION=sync
22 | QUEUE_NAME=whatever
23 |
24 | REDIS_HOST=redis
25 | REDIS_PASSWORD=null
26 | REDIS_PORT=6379
27 |
28 | MAIL_MAILER=log
29 |
30 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | ];
21 |
--------------------------------------------------------------------------------
/.env.travis:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_KEY=base64:WfpY+XDjPb2KYb9VDP7zyP4G7WBuB9rLHswC34DsNoc=
3 | APP_DEBUG=true
4 | APP_LOG_LEVEL=debug
5 | APP_URL=http://cronmon.test
6 |
7 | DB_CONNECTION=sqlite
8 | DB_DATABASE=:memory:
9 |
10 | BROADCAST_DRIVER=log
11 | CACHE_DRIVER=file
12 | SESSION_DRIVER=file
13 | QUEUE_DRIVER=sync
14 |
15 | REDIS_HOST=127.0.0.1
16 | REDIS_PASSWORD=null
17 | REDIS_PORT=6379
18 |
19 | MAIL_DRIVER=log
20 | MAIL_HOST=127.0.0.1
21 | MAIL_PORT=25
22 | MAIL_USERNAME=null
23 | MAIL_PASSWORD=null
24 | MAIL_ENCRYPTION=null
25 |
26 | PUSHER_APP_ID=
27 | PUSHER_KEY=
28 | PUSHER_SECRET=
29 |
30 | #SILENCED=1
31 |
32 |
--------------------------------------------------------------------------------
/.env.gitlab:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=testing
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_LOG_LEVEL=debug
6 | APP_URL=http://localhost
7 |
8 | LOG_CHANNEL=stack
9 |
10 | DB_CONNECTION=mysql
11 | DB_HOST=mysql
12 | DB_PORT=3306
13 | DB_DATABASE=homestead
14 | DB_USERNAME=homestead
15 | DB_PASSWORD=secret
16 |
17 | BROADCAST_DRIVER=log
18 | CACHE_DRIVER=file
19 | SESSION_DRIVER=file
20 | SESSION_LIFETIME=120
21 | QUEUE_CONNECTION=sync
22 | QUEUE_NAME=whatever
23 |
24 | REDIS_HOST=redis
25 | REDIS_PASSWORD=null
26 | REDIS_PORT=6379
27 |
28 | MAIL_DRIVER=log
29 |
30 | LDAP_SERVER=
31 | LDAP_OU=
32 | LDAP_USERNAME=
33 | LDAP_PASSWORD=
34 |
--------------------------------------------------------------------------------
/database/factories/TeamFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->name,
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
18 | })->describe('Display an inspiring quote');
19 |
--------------------------------------------------------------------------------
/resources/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Redirect Trailing Slashes If Not A Folder...
9 | RewriteCond %{REQUEST_FILENAME} !-d
10 | RewriteRule ^(.*)/$ /$1 [L,R=301]
11 |
12 | # Handle Front Controller...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_FILENAME} !-f
15 | RewriteRule ^ index.php [L]
16 |
17 | # Handle Authorization Header
18 | RewriteCond %{HTTP:Authorization} .
19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
20 |
21 |
--------------------------------------------------------------------------------
/database/factories/PingFactory.php:
--------------------------------------------------------------------------------
1 | \App\Models\Cronjob::factory(),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | $uri = urldecode(
11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
12 | );
13 |
14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the
15 | // built-in PHP web server. This provides a convenient way to test a Laravel
16 | // application without having installed a "real" web server software here.
17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
18 | return false;
19 | }
20 |
21 | require_once __DIR__.'/public/index.php';
22 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM uogsoe/soe-php-apache:7.2
2 |
3 | WORKDIR /var/www/html
4 |
5 | COPY docker/start.sh /usr/local/bin/start
6 | RUN chmod u+x /usr/local/bin/start
7 |
8 | COPY docker/ldap.conf /etc/ldap/ldap.conf
9 | COPY docker/install_composer.sh /tmp
10 | RUN chmod +x /tmp/install_composer.sh
11 |
12 | COPY . /var/www/html
13 |
14 | RUN /tmp/install_composer.sh
15 | RUN /usr/local/bin/php composer.phar install --no-dev
16 | RUN /usr/local/bin/php artisan key:generate
17 | RUN /usr/local/bin/php artisan view:clear
18 | RUN /usr/local/bin/php artisan config:cache
19 |
20 | RUN chown -R www-data:www-data /var/www/html
21 |
22 | CMD ["/usr/local/bin/start"]
23 |
--------------------------------------------------------------------------------
/.env.dusk.local:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_KEY=base64:H93DBDD2cqG+19YWhll8ruZinjTy8nsDwfMmbHX/vMk=
3 | APP_DEBUG=true
4 | APP_LOG_LEVEL=debug
5 | APP_URL=http://cronmon_github.test
6 |
7 | DB_CONNECTION=sqlite-testing
8 | DB_DATABASE=database/test_database.sqlite
9 |
10 | BROADCAST_DRIVER=log
11 | CACHE_DRIVER=file
12 | SESSION_DRIVER=file
13 | QUEUE_DRIVER=sync
14 |
15 | REDIS_HOST=127.0.0.1
16 | REDIS_PASSWORD=null
17 | REDIS_PORT=6379
18 |
19 | MAIL_DRIVER=log
20 | MAIL_HOST=
21 | MAIL_PORT=25
22 | MAIL_USERNAME=null
23 | MAIL_PASSWORD=null
24 | MAIL_ENCRYPTION=null
25 | MAIL_FROM_ADDRESS=
26 | MAIL_FROM_NAME=cronmon
27 |
28 | PUSHER_APP_ID=
29 | PUSHER_KEY=
30 | PUSHER_SECRET=
31 |
--------------------------------------------------------------------------------
/app/Http/Middleware/RedirectIfAuthenticated.php:
--------------------------------------------------------------------------------
1 | check()) {
21 | return redirect('/home');
22 | }
23 |
24 | return $next($request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | id === (int) $userId;
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/docker/app-healthcheck:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eo pipefail
4 |
5 | role=${CONTAINER_ROLE:-app}
6 |
7 | if [ "$role" = "app" ]; then
8 |
9 | curl -f http://localhost/ || exit 1
10 | exit 0
11 |
12 | elif [ "$role" = "queue" ]; then
13 |
14 | php /var/www/html/artisan horizon:status | grep -q 'Horizon is running' || exit 1
15 | exit 0
16 |
17 | elif [ "$role" = "scheduler" ]; then
18 |
19 | # need to figure something out for this... if at all checkable
20 | exit 0
21 |
22 | elif [ "$role" = "migrations" ]; then
23 |
24 | # nothing to do here
25 | exit 0
26 |
27 | else
28 | echo "Could not match the container role \"$role\""
29 | exit 1
30 | fi
31 |
--------------------------------------------------------------------------------
/resources/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/resources/views/team/partials/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 | Name
3 | Members
4 |
5 | @foreach ($teams as $team)
6 |
7 |
8 |
9 | {{ $team->name }}
10 |
11 |
12 |
13 | @foreach ($team->members as $member)
14 | {{ $member->username }}@if (!$loop->last), @endif
15 | @endforeach
16 |
17 |
18 | @endforeach
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const elixir = require('laravel-elixir');
2 |
3 | require('laravel-elixir-vue-2');
4 |
5 | /*
6 | |--------------------------------------------------------------------------
7 | | Elixir Asset Management
8 | |--------------------------------------------------------------------------
9 | |
10 | | Elixir provides a clean, fluent API for defining some basic Gulp tasks
11 | | for your Laravel application. By default, we are compiling the Sass
12 | | file for your application as well as publishing vendor resources.
13 | |
14 | */
15 |
16 | elixir((mix) => {
17 | mix.styles(['bulma.css', 'cronmon.css', 'animate.css', 'datatables.min.css'], 'public/css/app.css')
18 | .scripts(['datatables.min.js']);
19 | });
20 |
--------------------------------------------------------------------------------
/resources/views/home.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Overview
8 |
9 | Add job
10 |
11 |
12 |
13 |
17 |
18 |
19 | @endsection
20 |
--------------------------------------------------------------------------------
/resources/views/job/create.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Add new job
8 |
9 |
10 |
16 |
17 | @endsection
18 |
--------------------------------------------------------------------------------
/resources/views/profile/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit User
8 |
9 |
10 |
16 |
17 |
18 | @endsection
19 |
--------------------------------------------------------------------------------
/resources/views/team/create.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Add new team
8 |
9 |
10 |
16 |
17 | @endsection
18 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_KEY=
3 | APP_DEBUG=true
4 | APP_LOG_LEVEL=debug
5 | APP_URL=http://localhost
6 |
7 | DB_CONNECTION=mysql
8 | DB_HOST=127.0.0.1
9 | DB_PORT=3306
10 | DB_DATABASE=homestead
11 | DB_USERNAME=homestead
12 | DB_PASSWORD=secret
13 |
14 | BROADCAST_DRIVER=log
15 | CACHE_DRIVER=file
16 | SESSION_DRIVER=file
17 | QUEUE_DRIVER=sync
18 |
19 | REDIS_HOST=127.0.0.1
20 | REDIS_PASSWORD=null
21 | REDIS_PORT=6379
22 |
23 | MAIL_DRIVER=smtp
24 | MAIL_HOST=mailtrap.io
25 | MAIL_PORT=2525
26 | MAIL_USERNAME=null
27 | MAIL_PASSWORD=null
28 | MAIL_ENCRYPTION=null
29 | MAIL_FROM_ADDRESS=null
30 | MAIL_FROM_NAME=null
31 |
32 | PUSHER_APP_ID=
33 | PUSHER_KEY=
34 | PUSHER_SECRET=
35 |
36 | # use this to silence _all_ alarms
37 | #SILENCED=1
38 |
39 |
--------------------------------------------------------------------------------
/resources/views/user/create.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Create new user
8 |
9 |
10 |
16 |
17 |
18 | @endsection
19 |
--------------------------------------------------------------------------------
/resources/views/template/create.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Add new template
8 |
9 |
10 |
16 |
17 | @endsection
18 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
17 | 'App\Listeners\EventListener',
18 | ],
19 | ];
20 |
21 | /**
22 | * Register any events for your application.
23 | *
24 | * @return void
25 | */
26 | public function boot()
27 | {
28 | parent::boot();
29 |
30 | //
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/resources/views/template/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit template
8 |
9 |
10 |
16 |
17 | @endsection
18 |
--------------------------------------------------------------------------------
/.env.qa:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=testing
3 | APP_KEY=base64:dXXkGlTYDXTG7OD0FTx1EdRAFW2icInDFHsG4K8wipI=
4 | APP_DEBUG=true
5 | APP_LOG_LEVEL=debug
6 | APP_URL=http://localhost
7 |
8 | LOG_CHANNEL=stack
9 |
10 | DB_CONNECTION=mysql
11 | DB_HOST=mysql
12 | DB_PORT=3306
13 | DB_DATABASE=homestead
14 | DB_USERNAME=homestead
15 | DB_PASSWORD=secret
16 |
17 | BROADCAST_DRIVER=log
18 | CACHE_DRIVER=redis
19 | SESSION_DRIVER=redis
20 | SESSION_LIFETIME=120
21 | QUEUE_CONNECTION=redis
22 | QUEUE_NAME=whatever
23 |
24 | REDIS_HOST=redis
25 | REDIS_PASSWORD=null
26 | REDIS_PORT=6379
27 |
28 | MAIL_DRIVER=smtp
29 | MAIL_HOST==mailhog
30 | MAIL_PORT=1025
31 | MAIL_USERNAME=null
32 | MAIL_PASSWORD=null
33 | MAIL_ENCRYPTION=null
34 |
35 | LDAP_SERVER=
36 | LDAP_OU=
37 | LDAP_USERNAME=
38 | LDAP_PASSWORD=
39 |
--------------------------------------------------------------------------------
/tests/Browser/Pages/HomePage.php:
--------------------------------------------------------------------------------
1 | '#selector',
38 | ];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_28_085108_create_teams_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('name');
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('teams');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_21_113124_create_pings_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->unsignedInteger('cronjob_id');
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('pings');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2016_12_08_085236_add_data_field_to_pings.php:
--------------------------------------------------------------------------------
1 | text('data')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('pings', function (Blueprint $table) {
29 | $table->dropColumn('data');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2016_12_02_132330_add_notes_to_cronjobs.php:
--------------------------------------------------------------------------------
1 | text('notes')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('notes');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_12_135332_add_api_key_field_to_users_table.php:
--------------------------------------------------------------------------------
1 | string('api_key')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropColumn('api_key');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_28_102238_add_team_id_to_cronjobs_table.php:
--------------------------------------------------------------------------------
1 | unsignedInteger('team_id')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('team_id');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2016_12_12_084446_add_is_logging_to_cronjobs_table.php:
--------------------------------------------------------------------------------
1 | boolean('is_logging')->default(true);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('is_logging');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_12_19_130752_add_cron_schedule_to_cronjobs.php:
--------------------------------------------------------------------------------
1 | string('cron_schedule')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('cron_schedule');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class))->bootstrap();
26 |
27 | foreach ($commands as $command) {
28 | $console->call($command);
29 | }
30 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_18_134052_add_is_silenced_flag_to_users_table.php:
--------------------------------------------------------------------------------
1 | boolean('is_silenced')->default(false);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropColumn('is_silenced');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2020_10_14_103632_add_uuid_to_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | string('uuid')->after('id')->nullable()->unique();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('failed_jobs', function (Blueprint $table) {
29 | $table->dropColumn('uuid');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2018_01_05_102612_add_fallback_email_to_cronjobs_table.php:
--------------------------------------------------------------------------------
1 | string('fallback_email')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('fallback_email');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_password_resets_table.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
18 | $table->string('token')->index();
19 | $table->timestamp('created_at')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::dropIfExists('password_resets');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/UuidTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(1, preg_match('/(\w{8}(-\w{4}){3}-\w{12}?)/', $uuid));
18 | }
19 |
20 | public function test_each_call_to_uuid_is_unique()
21 | {
22 | $uuids = [];
23 | foreach (range(1, 100) as $count) {
24 | $uuids[] = CronUuid::generate();
25 | }
26 | $uniqueUuids = array_unique($uuids);
27 | $this->assertEquals(count($uuids), count($uniqueUuids));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Passwords must be at least six characters and match the confirmation.',
17 | 'reset' => 'Your password has been reset!',
18 | 'sent' => 'We have e-mailed your password reset link!',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that e-mail address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_22_142917_add_last_alerted_to_cronjobs_table.php:
--------------------------------------------------------------------------------
1 | dateTime('last_alerted')->default('2000-01-01 01:01:01');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('cronjobs', function (Blueprint $table) {
29 | $table->dropColumn('last_alerted');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DuskTestCase.php:
--------------------------------------------------------------------------------
1 | $this->faker->name,
27 | 'email' => $this->faker->unique()->safeEmail,
28 | 'password' => '$2y$10$OGsEr5fHNbvU2Tlr4VvvZ.8HuZP02Tt78SiGwwzul7w9.I50ewQhy', // secret
29 | 'remember_token' => Str::random(10),
30 | 'is_admin' => false,
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Console/Commands/SilenceAlerts.php:
--------------------------------------------------------------------------------
1 | $this->filterTemplates(),
18 | ]);
19 | }
20 |
21 | public function filterTemplates()
22 | {
23 | $query = auth()->user()->templates()
24 | ->with(['user', 'team'])
25 | ->where('name', 'like', "%{$this->filter}%");
26 | if ($this->teams) {
27 | $teamIds = auth()->user()->teams()->get()->pluck('id')->values()->toArray();
28 | $query = $query->whereIn('team_id', $teamIds);
29 | }
30 |
31 | return $query->orderBy('name')->get();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_28_085241_create_team_user_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->unsignedInteger('user_id');
19 | $table->unsignedInteger('team_id');
20 | $table->boolean('is_admin')->default(false);
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('team_user');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/resources/views/auth/passwords/email.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 |
4 | @section('content')
5 |
6 |
7 |
8 |
Reset Password
9 |
21 |
22 |
23 |
24 | @endsection
25 |
--------------------------------------------------------------------------------
/app/Http/Controllers/TeamMemberController.php:
--------------------------------------------------------------------------------
1 | authorize('edit-team', $team);
15 | $users = User::orderBy('username')->get();
16 |
17 | return view('team.member.edit', compact('team', 'users'));
18 | }
19 |
20 | public function update($id, Request $request)
21 | {
22 | $team = Team::findOrFail($id);
23 | $this->authorize('edit-team', $team);
24 | if ($request->filled('remove')) {
25 | $team->removeMembers($request->remove);
26 | }
27 | if ($request->filled('add')) {
28 | $team->addMember($request->add);
29 | }
30 |
31 | return redirect()->route('team.show', $team->id);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2019_07_02_150701_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->text('connection');
19 | $table->text('queue');
20 | $table->longText('payload');
21 | $table->longText('exception');
22 | $table->timestamp('failed_at')->useCurrent();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('failed_jobs');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/resources/views/user/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit User
8 |
12 |
13 |
14 |
20 |
21 | @endsection
22 |
--------------------------------------------------------------------------------
/public/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ForgotPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('username')->unique();
19 | $table->string('email')->unique();
20 | $table->string('password');
21 | $table->boolean('is_admin')->default(false);
22 | $table->rememberToken();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('users');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/factories/TemplateFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->text(30),
27 | 'slug' => $this->faker->slug,
28 | 'user_id' => User::factory(),
29 | 'uuid' => $this->faker->uuid,
30 | 'grace' => 5,
31 | 'grace_units' => 'minute',
32 | 'period' => 1,
33 | 'period_units' => 'hour',
34 | 'email' => '',
35 | 'team_id' => null,
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Console/Commands/CheckJobs.php:
--------------------------------------------------------------------------------
1 | each(function ($user, $key) {
42 | $user->checkJobs();
43 | });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/docker/start.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | role=${CONTAINER_ROLE:-app}
6 | env=${APP_ENV:-production}
7 |
8 | until nc -z -v -w30 mysql 3306
9 | do
10 | echo "Waiting for database connection..."
11 | # wait for 5 seconds before check again
12 | sleep 5
13 | done
14 |
15 | if [ "$env" != "local" ]; then
16 | echo "Caching configuration..."
17 | (cd /var/www/html && php artisan config:cache && php artisan route:cache && php artisan view:cache)
18 | fi
19 |
20 | if [ "$role" = "app" ]; then
21 |
22 | php /var/www/html/artisan migrate
23 | exec apache2-foreground
24 |
25 | elif [ "$role" = "queue" ]; then
26 |
27 | echo "Running the queue..."
28 | php /var/www/html/artisan queue:work
29 |
30 | elif [ "$role" = "scheduler" ]; then
31 |
32 | while [ true ]
33 | do
34 | php /var/www/html/artisan schedule:run --verbose --no-interaction &
35 | sleep 60
36 | done
37 |
38 | else
39 | echo "Could not match the container role \"$role\""
40 | exit 1
41 | fi
42 |
--------------------------------------------------------------------------------
/resources/views/job/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
23 |
24 | @include('job.partials.index')
25 |
26 | @if (Auth::user()->getTeamJobs()->count() > 0)
27 |
28 | @include('job.partials.index', ['jobs' => Auth::user()->getTeamJobs()])
29 |
30 | @endif
31 | @endsection
32 |
--------------------------------------------------------------------------------
/resources/views/team/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit team
8 |
13 |
14 |
15 |
21 |
22 | @endsection
23 |
--------------------------------------------------------------------------------
/resources/views/job/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit job {{ $job->name }}
8 |
12 |
13 |
14 |
20 |
21 | @endsection
22 |
--------------------------------------------------------------------------------
/resources/css/cronmon.css:
--------------------------------------------------------------------------------
1 | @tailwind preflight;
2 |
3 | @tailwind components;
4 |
5 | a {
6 | @apply no-underline;
7 | }
8 |
9 | .label {
10 | @apply block text-grey-darker text-sm font-bold mb-2;
11 | }
12 |
13 | .dataTables_filter input {
14 | @apply appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight;
15 | }
16 |
17 | .title {
18 | @apply text-grey-dark text-lg font-light;
19 | }
20 |
21 | .input {
22 | @apply shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight;
23 | }
24 | .input:focus {
25 | @apply border-orange-dark;
26 | }
27 | .button {
28 | @apply border border-orange text-orange-dark font-bold py-2 px-4 rounded;
29 | }
30 | .button:hover {
31 | @apply bg-orange text-white;
32 | }
33 | .button:focus {
34 | @apply bg-orange;
35 | }
36 |
37 | .button-danger {
38 | @apply border border-red text-red-dark font-bold py-2 px-4 rounded;
39 | }
40 | .button-danger:hover {
41 | @apply bg-red text-white;
42 | }
43 | .button-danger:focus {
44 | @apply bg-red;
45 | }
46 | @tailwind utilities;
47 |
--------------------------------------------------------------------------------
/resources/views/user/show.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | User details for {{ $user->username }}
8 |
9 | Edit user
10 |
11 |
12 |
13 | @include('user.partials.profile')
14 |
15 |
16 |
17 |
18 |
19 | Jobs
20 |
21 |
22 | @include('job.partials.index', ['jobs' => $user->getAvailableJobs()])
23 |
24 |
25 | @endsection
26 |
--------------------------------------------------------------------------------
/app/Rules/ValidCronExpression.php:
--------------------------------------------------------------------------------
1 | truncatePings(config('cronmon.keep_pings', 100));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/phpunit-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2.4"
2 |
3 | # See https://github.com/UoGSoE/docker-stuff for the origins of this file
4 |
5 | services:
6 | phpunit:
7 | build:
8 | context: .
9 | dockerfile: phpunit.Dockerfile
10 | args:
11 | PHP_VERSION: 7.3
12 | depends_on:
13 | mysql:
14 | condition: service_healthy
15 | tmpfs:
16 | - /var/www/html/storage/logs
17 | - /var/www/html/storage/framework/cache
18 | environment:
19 | DB_CONNECTION: mysql
20 | DB_HOST: mysql
21 | DB_DATABASE: homestead
22 | DB_USERNAME: homestead
23 | DB_PASSWORD: secret
24 | volumes:
25 | - .:/var/www/html:delegated
26 |
27 | mysql:
28 | image: mysql:5.7
29 | environment:
30 | MYSQL_ROOT_PASSWORD: root
31 | MYSQL_DATABASE: homestead
32 | MYSQL_USER: homestead
33 | MYSQL_PASSWORD: secret
34 | healthcheck:
35 | test: /usr/bin/mysql --host=127.0.0.1 --user=homestead --password=secret --silent --execute \"SELECT 1;\"
36 | interval: 3s
37 | timeout: 20s
38 | retries: 5
39 |
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 billy@monkeytwizzle.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/database/factories/CronjobFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->unique()->safeEmail,
27 | 'name' => $this->faker->word(),
28 | 'grace' => 5,
29 | 'grace_units' => 'minute',
30 | 'period' => 1,
31 | 'period_units' => 'hour',
32 | 'user_id' => \App\Models\User::factory(),
33 | 'email' => 'test@test.com',
34 | 'last_run' => null,
35 | 'is_silenced' => false,
36 | 'uuid' => CronUuid::generate(),
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/config/compile.php:
--------------------------------------------------------------------------------
1 | [
17 | //
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled File Providers
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may list service providers which define a "compiles" function
26 | | that returns additional files that should be compiled, providing an
27 | | easy way to get common files from any packages you are utilizing.
28 | |
29 | */
30 |
31 | 'providers' => [
32 | //
33 | ],
34 |
35 | ];
36 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProfileController.php:
--------------------------------------------------------------------------------
1 | Auth::user()]);
14 | }
15 |
16 | public function edit()
17 | {
18 | return view('profile.edit', ['user' => Auth::user()]);
19 | }
20 |
21 | public function update(Request $request)
22 | {
23 | $data = $this->validate($request, [
24 | 'username' => ['required', Rule::unique('users')->ignore($request->user()->id)],
25 | 'email' => ['required', 'email', Rule::unique('users')->ignore($request->user()->id)],
26 | ]);
27 | $request->user()->fill($data);
28 | if ($request->filled('new_api_key')) {
29 | $key = $request->user()->generateNewApiKey();
30 | session()->flash('success', $key);
31 | }
32 | $request->user()->save();
33 |
34 | return redirect()->route('profile.show');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ResetPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/Models/Team.php:
--------------------------------------------------------------------------------
1 | belongsToMany(User::class);
17 | }
18 |
19 | public function jobs()
20 | {
21 | return $this->hasMany(Cronjob::class);
22 | }
23 |
24 | public function removeMembers($userIds)
25 | {
26 | return $this->members()->detach($userIds);
27 | }
28 |
29 | public function addMember($userId)
30 | {
31 | if ($this->isAlreadyAMember($userId)) {
32 | return false;
33 | }
34 |
35 | return $this->members()->attach($userId);
36 | }
37 |
38 | protected function isAlreadyAMember($userId)
39 | {
40 | return $this->members()->where('user_id', $userId)->first();
41 | }
42 |
43 | public function delete()
44 | {
45 | $this->jobs->each->update(['team_id' => null]);
46 | parent::delete();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/Providers/HorizonServiceProvider.php:
--------------------------------------------------------------------------------
1 | email, [
38 | //
39 | ]);
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/resources/views/layouts/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ config('app.name', 'Laravel') }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 | @livewireStyles
23 |
24 |
25 |
26 |
27 | @include('partials.navbar')
28 |
29 |
30 | @include('partials.errors')
31 | @yield('content')
32 |
33 |
34 |
35 |
36 |
37 | @livewireScripts
38 |
39 |
40 |
--------------------------------------------------------------------------------
/bootstrap/autoload.php:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/Feature
15 |
16 |
17 |
18 | ./tests/Unit
19 |
20 |
21 |
22 |
23 | ./app
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/Http/Requests/StoreCronjob.php:
--------------------------------------------------------------------------------
1 | ['required', 'max:255', Rule::unique('cronjobs')->where(function ($query) use ($request) {
32 | $query->where('user_id', $request->user()->id);
33 | })],
34 | 'period' => 'required|min:1',
35 | 'period_units' => 'required|in:minute,day,hour,week',
36 | 'grace' => 'required|min:1',
37 | 'grace_units' => 'required|in:minute,day,hour,week',
38 | 'cron_schedule' => ['nullable', new ValidCronExpression],
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/docker/app-start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | role=${CONTAINER_ROLE:-app}
6 | env=${APP_ENV:-production}
7 |
8 | until nc -z -v -w30 mysql 3306
9 | do
10 | echo "Waiting for database connection..."
11 | sleep 5
12 | done
13 |
14 | until echo 'PING' | nc -w 1 redis 6379 | grep -q PONG
15 | do
16 | echo "Waiting for Redis connection..."
17 | sleep 5
18 | done
19 |
20 | php /var/www/html/artisan config:cache
21 |
22 | if [ "$role" = "app" ]; then
23 |
24 | exec apache2-foreground
25 |
26 | elif [ "$role" = "queue" ]; then
27 |
28 | php /var/www/html/artisan horizon
29 |
30 | elif [ "$role" = "scheduler" ]; then
31 |
32 | while [ true ]
33 | do
34 | php /var/www/html/artisan schedule:run --verbose --no-interaction &
35 | sleep 60
36 | done
37 |
38 | elif [ "$role" = "migrations" ]; then
39 |
40 | php /var/www/html/artisan migrate --force
41 | php /var/www/html/artisan cronmon:autocreateadmin
42 |
43 | while [ true ]
44 | do
45 | sleep 86400
46 | done
47 |
48 | elif [ "$role" = "test" ]; then
49 |
50 | php /var/www/html/vendor/bin/phpunit --colors=never
51 |
52 | else
53 | echo "Could not match the container role \"$role\""
54 | exit 1
55 | fi
56 |
--------------------------------------------------------------------------------
/database/migrations/2016_11_17_114550_create_cronjobs_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('name');
19 | $table->integer('grace');
20 | $table->string('grace_units');
21 | $table->integer('period');
22 | $table->string('period_units');
23 | $table->unsignedInteger('user_id');
24 | $table->string('uuid');
25 | $table->string('email')->nullable();
26 | $table->datetime('last_run')->nullable();
27 | $table->boolean('is_silenced')->default(false);
28 | $table->timestamps();
29 | });
30 | }
31 |
32 | /**
33 | * Reverse the migrations.
34 | *
35 | * @return void
36 | */
37 | public function down()
38 | {
39 | Schema::dropIfExists('cronjobs');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests
14 | ./tests/BrowserKit
15 | ./tests/Browser
16 |
17 |
18 |
19 |
20 | ./app
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resources/views/team/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | All teams
8 |
9 |
10 |
11 |
12 | Name
13 |
14 |
15 | No. Members
16 |
17 |
18 | No. Jobs
19 |
20 |
21 | @foreach ($teams as $team)
22 |
35 | @endforeach
36 |
37 | @endsection
38 |
--------------------------------------------------------------------------------
/tests/ApiTest.php:
--------------------------------------------------------------------------------
1 | create();
17 | $job = $this->createRunningJob($user);
18 |
19 | $this->get('/ping/'.$job->uuid)->assertResponseOk();
20 |
21 | $jobCopy = $user->jobs()->first();
22 | $this->assertTrue($jobCopy->last_run->gt($job->last_run));
23 | }
24 |
25 | public function test_pinging_an_awol_jobs_uri_updates_its_status()
26 | {
27 | $user = User::factory()->create();
28 | $job = $this->createAwolJob($user);
29 | $this->assertTrue($job->isAwol());
30 |
31 | $this->get('/ping/'.$job->uuid)->assertResponseOk();
32 |
33 | $job = $job->fresh();
34 | $this->assertFalse($job->isAwol());
35 | }
36 |
37 | public function test_pinging_a_nonexistant_uri_fails()
38 | {
39 | $this->get('/ping/hellokitty')->assertResponseStatus(404);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Requests/StoreTemplate.php:
--------------------------------------------------------------------------------
1 | ['required', 'max:255', Rule::unique('templates')->where(function ($query) use ($request) {
32 | $query->where('user_id', $request->user()->id);
33 | })],
34 | 'period' => 'required|min:1',
35 | 'period_units' => 'required|in:minute,day,hour,week',
36 | 'grace' => 'required|min:1',
37 | 'grace_units' => 'required|in:minute,day,hour,week',
38 | 'cron_schedule' => ['nullable', new ValidCronExpression],
39 | 'team_id' => 'nullable|integer',
40 | 'email' => 'nullable|email',
41 | ];
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/resources/views/errors/503.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Be right back.
5 |
6 |
7 |
8 |
39 |
40 |
41 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/docker-compose-prod.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | services:
4 | app:
5 | image: 127.0.0.1/cronmon
6 | environment:
7 | CONTAINER_ROLE: app
8 | build:
9 | context: .
10 | dockerfile: docker/Dockerfile
11 | depends_on:
12 | - redis
13 | networks:
14 | - private
15 | - mysql-router
16 | expose:
17 | - "80"
18 | secrets:
19 | - source: dotenv
20 | target: .env
21 |
22 | scheduler:
23 | image: 127.0.0.1/cronmon
24 | environment:
25 | CONTAINER_ROLE: scheduler
26 | depends_on:
27 | - app
28 | networks:
29 | - private
30 | - mysql-router
31 | secrets:
32 | - source: dotenv
33 | target: .env
34 |
35 | queue:
36 | image: 127.0.0.1/cronmon
37 | environment:
38 | CONTAINER_ROLE: queue
39 | depends_on:
40 | - app
41 | networks:
42 | - private
43 | - mysql-router
44 | secrets:
45 | - source: dotenv
46 | target: .env
47 |
48 | redis:
49 | image: redis:4
50 | networks:
51 | - private
52 | volumes:
53 | - redis:/data
54 |
55 | volumes:
56 | redis:
57 | driver: "local"
58 |
59 | networks:
60 | private:
61 | mysql-router:
62 | external: true
63 |
64 | secrets:
65 | dotenv:
66 | external: true
67 | name: cronmon-dotenv-2019-03-29
68 |
69 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('cronmon:checkjobs')->everyFiveMinutes()->withoutOverlapping();
35 | $schedule->command('cronmon:truncatepings')->weekly();
36 | }
37 |
38 | /**
39 | * Register the Closure based commands for the application.
40 | *
41 | * @return void
42 | */
43 | protected function commands()
44 | {
45 | require base_path('routes/console.php');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/resources/views/auth/passwords/reset.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
Reset Password
8 |
27 |
28 |
29 |
30 | @endsection
31 |
--------------------------------------------------------------------------------
/app/Http/Requests/UpdateTemplate.php:
--------------------------------------------------------------------------------
1 | [
32 | 'required',
33 | 'max:255',
34 | Rule::unique('templates')->ignore($this->id)->where(function ($query) use ($request) {
35 | $query->where('user_id', $request->user()->id);
36 | }),
37 | ],
38 | 'period' => 'required|min:1',
39 | 'period_units' => 'required|in:minute,day,hour,week',
40 | 'grace' => 'required|min:1',
41 | 'grace_units' => 'required|in:minute,day,hour,week',
42 | 'cron_schedule' => ['nullable', new ValidCronExpression],
43 | 'team_id' => 'nullable|integer',
44 | 'email' => 'nullable|email',
45 | ];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | name('ping.get');
17 | Route::post('/ping/{uuid}', [App\Http\Controllers\ApiController::class, 'ping'])->name('ping.post');
18 | Route::post('/api/templates/{slug}', [App\Http\Controllers\Api\TemplateController::class, 'store'])->name('api.template.create_job');
19 |
20 | Route::get('/api/cronjob/{uuid}', [App\Http\Controllers\Api\CronjobController::class, 'show'])->name('api.cronjob.show');
21 |
22 | // POST job -- create a new job - returns json of the job
23 | // POST job/{uuid} -- update a job - returns json of the job
24 | // POST job/{uuid}/silence -- silence a job
25 | // POST job/{uuid}/unsilence -- unsilence a job
26 | // GET job/{uuid}?token={token} -- return json of specific job
27 | // DELETE job/{uuid}?token={token} -- delete a given job
28 |
29 | //Route::get('/user', function (Request $request) {
30 | // return $request->user();
31 | //})->middleware('auth:api');
32 |
--------------------------------------------------------------------------------
/resources/views/auth/login.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
26 | @endsection
27 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | 'hellothere',
22 | 'grace' => 5,
23 | 'grace_units' => 'minute',
24 | 'period' => 1,
25 | 'period_units' => 'hour',
26 | 'email' => '',
27 | 'is_silenced' => false,
28 | 'team_id' => null,
29 | ];
30 |
31 | public function createAwolJob($user, $data = [])
32 | {
33 | $data = array_merge($this->jobData, $data);
34 | $job = $user->addNewJob($data);
35 | $job->last_run = Carbon::now()->subHours(2);
36 | $job->last_alerted = Carbon::now()->subHours(2);
37 | $job->save();
38 |
39 | return $job;
40 | }
41 |
42 | public function createRunningJob($user, $data = [])
43 | {
44 | $data = array_merge($this->jobData, $data);
45 | $job = $user->addNewJob($data);
46 | $job->last_run = Carbon::now()->subMinutes(2);
47 | $job->save();
48 |
49 | return $job;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/resources/js/components/JobTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
44 |
--------------------------------------------------------------------------------
/resources/assets/js/components/JobTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
44 |
--------------------------------------------------------------------------------
/resources/views/user/partials/profile.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Username
4 |
5 | {{ $user->username }}
6 |
7 |
8 |
9 |
Email
10 |
11 | @if (Auth::user()->is_admin)
12 | {{ $user->email }}
13 | @else
14 | {{{ $user->email }}}
15 | @endif
16 |
17 |
18 |
19 |
Admin?
20 |
21 | {{{ $user->is_admin ? 'Yes' : 'No' }}}
22 |
23 |
24 |
25 |
Silenced Alarms?
26 |
27 | {{{ $user->is_silenced ? 'Yes' : 'No' }}}
28 |
29 |
30 |
31 |
32 | @if ($user->api_key)
33 |
34 |
35 |
Api Key
36 |
37 |
38 |
39 |
40 |
41 | @endif
42 |
--------------------------------------------------------------------------------
/resources/views/profile/show.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 | My details
9 |
10 | Edit
11 |
12 |
13 |
14 | @include('user.partials.profile')
15 |
16 |
17 |
18 |
19 |
20 |
21 | My Teams
22 |
23 | Add new team
24 |
25 |
26 |
27 | @include('team.partials.index', ['teams' => $user->teams])
28 |
29 |
30 |
31 |
32 |
33 |
My Jobs
34 |
35 | @include('job.partials.index', ['jobs' => $user->getAvailableJobs()])
36 |
37 | @endsection
38 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21 | ],
22 |
23 | 'postmark' => [
24 | 'token' => env('POSTMARK_TOKEN'),
25 | ],
26 |
27 | 'ses' => [
28 | 'key' => env('AWS_ACCESS_KEY_ID'),
29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
31 | ],
32 |
33 | 'sparkpost' => [
34 | 'secret' => env('SPARKPOST_SECRET'),
35 | ],
36 |
37 | 'stripe' => [
38 | 'model' => App\Models\User::class,
39 | 'key' => env('STRIPE_KEY'),
40 | 'secret' => env('STRIPE_SECRET'),
41 | 'webhook' => [
42 | 'secret' => env('STRIPE_WEBHOOK_SECRET'),
43 | 'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
44 | ],
45 | ],
46 |
47 | ];
48 |
--------------------------------------------------------------------------------
/resources/views/user/partials/form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | @if (Auth::user()->is_admin)
13 |
17 | @endif
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 | @if (Auth::user()->is_admin and $user->id)
39 |
42 |
43 | @endif
44 |
--------------------------------------------------------------------------------
/tests/BrowserKitTest.php:
--------------------------------------------------------------------------------
1 | 'hellothere',
24 | 'grace' => 5,
25 | 'grace_units' => 'minute',
26 | 'period' => 1,
27 | 'period_units' => 'hour',
28 | 'email' => '',
29 | 'is_silenced' => false,
30 | 'team_id' => null,
31 | ];
32 |
33 | public function createAwolJob($user, $data = [])
34 | {
35 | $data = array_merge($this->jobData, $data);
36 | $job = $user->addNewJob($data);
37 | $job->last_run = Carbon::now()->subHours(2);
38 | $job->last_alerted = Carbon::now()->subHours(2);
39 | $job->save();
40 |
41 | return $job;
42 | }
43 |
44 | public function createRunningJob($user, $data = [])
45 | {
46 | $data = array_merge($this->jobData, $data);
47 | $job = $user->addNewJob($data);
48 | $job->last_run = Carbon::now()->subMinutes(2);
49 | $job->save();
50 |
51 | return $job;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/database/migrations/2020_03_26_101351_create_templates_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('name');
19 | $table->string('slug')->nullable(); // nullable as we generate the slug after it's created
20 | $table->integer('grace');
21 | $table->string('grace_units');
22 | $table->integer('period');
23 | $table->string('period_units');
24 | $table->string('cron_schedule')->nullable();
25 | $table->unsignedInteger('user_id');
26 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
27 | $table->string('uuid');
28 | $table->string('email')->nullable();
29 | $table->unsignedInteger('team_id')->nullable();
30 | $table->foreign('team_id')->references('id')->on('teams')->onDelete('set null');
31 | $table->timestamps();
32 | });
33 | }
34 |
35 | /**
36 | * Reverse the migrations.
37 | *
38 | * @return void
39 | */
40 | public function down()
41 | {
42 | Schema::dropIfExists('templates');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Controllers/TemplateController.php:
--------------------------------------------------------------------------------
1 | $template,
22 | ]);
23 | }
24 |
25 | public function create()
26 | {
27 | return view('template.create', [
28 | 'template' => new Template,
29 | 'users' => User::orderBy('username')->get(),
30 | ]);
31 | }
32 |
33 | public function store(StoreTemplate $request)
34 | {
35 | $request->user()->addNewTemplate($request->validated());
36 |
37 | return redirect(route('template.index'));
38 | }
39 |
40 | public function edit(Template $template)
41 | {
42 | $this->authorize('view', $template);
43 |
44 | return view('template.edit', [
45 | 'template' => $template,
46 | 'users' => User::orderBy('username')->get(),
47 | ]);
48 | }
49 |
50 | public function update(Template $template, UpdateTemplate $request)
51 | {
52 | $this->authorize('update', $template);
53 |
54 | $template->update($request->validated());
55 | $template->updateSlug();
56 |
57 | return redirect(route('template.index'));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'email'];
23 | foreach ($mails as $mail) {
24 | $data = ['email' => $mail];
25 | $validator = Validator::make($data, $rules);
26 | if ($validator->fails()) {
27 | return false;
28 | }
29 | }
30 |
31 | return true;
32 | });
33 | // fix for laravel 5.4 using multibyte strings which breaks on older mysql/mariadb
34 | Schema::defaultStringLength(191);
35 | if (env('FORCE_HTTPS', false)) { // Default value should be false for local server
36 | URL::forceSchema('https');
37 | }
38 | }
39 |
40 | /**
41 | * Register any application services.
42 | *
43 | * @return void
44 | */
45 | public function register()
46 | {
47 | if ($this->app->environment('local', 'testing')) {
48 | $this->app->register(DuskServiceProvider::class);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/Http/Controllers/TeamController.php:
--------------------------------------------------------------------------------
1 | with('members', 'jobs')->get();
13 |
14 | return view('team.index', compact('teams'));
15 | }
16 |
17 | public function create()
18 | {
19 | $team = new Team;
20 |
21 | return view('team.create', compact('team'));
22 | }
23 |
24 | public function store(Request $request)
25 | {
26 | $team = new Team($request->only('name'));
27 | $request->user()->teams()->save($team);
28 |
29 | return redirect()->route('team.show', $team->id);
30 | }
31 |
32 | public function show(Team $team)
33 | {
34 | $this->authorize('edit-team', $team);
35 |
36 | return view('team.show', compact('team'));
37 | }
38 |
39 | public function edit(Team $team)
40 | {
41 | $this->authorize('edit-team', $team);
42 |
43 | return view('team.edit', compact('team'));
44 | }
45 |
46 | public function update(Request $request, Team $team)
47 | {
48 | $this->authorize('edit-team', $team);
49 | $team->fill($request->only('name'));
50 | $team->save();
51 |
52 | return redirect()->route('team.show', $team->id);
53 | }
54 |
55 | public function destroy(Team $team)
56 | {
57 | $this->authorize('edit-team', $team);
58 |
59 | $team->delete();
60 |
61 | return redirect()->route('home');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Console/Commands/CreateAdmin.php:
--------------------------------------------------------------------------------
1 | argument('username');
43 | $email = $this->argument('email');
44 | $validator = Validator::make(['username' => $username, 'email' => $email], [
45 | 'username' => 'required|unique:users|max:255',
46 | 'email' => 'required|email|unique:users|max:255',
47 | ]);
48 | if ($validator->fails()) {
49 | foreach ($validator->errors()->all() as $error) {
50 | $this->error($error);
51 | }
52 | throw new \RuntimeException('Aborting');
53 | }
54 | User::createNewAdmin($username, $email);
55 | $this->info('User created - password notification sent');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/resources/views/user/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Current Users
8 |
9 | Add user
10 |
11 |
12 |
13 |
14 | Username
15 | Email
16 | No. Jobs
17 | Is Admin?
18 |
19 | @foreach ($users as $user)
20 |
21 |
22 |
23 | {{ $user->username }}
24 |
25 | @if ($user->is_silenced)
26 |
27 |
28 |
29 | @endif
30 |
31 |
32 | {{ $user->email }}
33 |
34 |
35 | No. Jobs:
36 | {{ $user->jobs()->count() }}
37 |
38 |
39 | Admin?
40 | {{ $user->is_admin ? 'Yes' : 'No' }}
41 |
42 |
43 | @endforeach
44 |
45 |
46 | @endsection
--------------------------------------------------------------------------------
/app/Http/Requests/UpdateCronjob.php:
--------------------------------------------------------------------------------
1 | user()->is_admin) {
20 | return true;
21 | }
22 | $job = Cronjob::findOrFail($this->id);
23 | if ($this->user()->id == $job->user_id) {
24 | return true;
25 | }
26 | if ($this->user()->onTeam($job->team_id)) {
27 | return true;
28 | }
29 |
30 | return false;
31 | }
32 |
33 | /**
34 | * Get the validation rules that apply to the request.
35 | *
36 | * @return array
37 | */
38 | public function rules()
39 | {
40 | $request = $this;
41 |
42 | return [
43 | 'name' => [
44 | 'required',
45 | 'max:255',
46 | Rule::unique('cronjobs')->ignore($this->id)->where(function ($query) use ($request) {
47 | $query->where('user_id', $request->user()->id);
48 | }),
49 | ],
50 | 'period' => 'required|min:1',
51 | 'period_units' => 'required|in:minute,day,hour,week',
52 | 'grace' => 'required|min:1',
53 | 'grace_units' => 'required|in:minute,day,hour,week',
54 | 'email' => 'emails',
55 | 'cron_schedule' => ['nullable', new ValidCronExpression],
56 | ];
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_KEY'),
36 | 'secret' => env('PUSHER_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | //
40 | ],
41 | ],
42 |
43 | 'redis' => [
44 | 'driver' => 'redis',
45 | 'connection' => 'default',
46 | ],
47 |
48 | 'log' => [
49 | 'driver' => 'log',
50 | ],
51 |
52 | 'null' => [
53 | 'driver' => 'null',
54 | ],
55 |
56 | ],
57 |
58 | ];
59 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/LoginController.php:
--------------------------------------------------------------------------------
1 | middleware('guest', ['except' => 'logout']);
39 | }
40 |
41 | protected function validateLogin(Request $request)
42 | {
43 | $field = filter_var($request->input('login'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
44 | $request->merge([$field => $request->input('login')]);
45 | $this->validate($request, [
46 | $field => 'required', 'password' => 'required',
47 | ]);
48 | }
49 |
50 | protected function credentials(Request $request)
51 | {
52 | $field = filter_var($request->input('login'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
53 |
54 | return $request->only($field, 'password');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/DatabaseSetup.php:
--------------------------------------------------------------------------------
1 | isInMemory()) {
14 | $this->setupInMemoryDatabase();
15 | } else {
16 | $this->setupTestDatabase();
17 | }
18 | }
19 |
20 | protected function isInMemory()
21 | {
22 | return config('database.connections')[config('database.default')]['database'] == ':memory:';
23 | }
24 |
25 | protected function setupInMemoryDatabase()
26 | {
27 | $this->artisan('migrate');
28 | $this->app[Kernel::class]->setArtisan(null);
29 | }
30 |
31 | protected function setupTestDatabase()
32 | {
33 | if (! static::$migrated) {
34 | $this->artisan('migrate:refresh');
35 | $this->app[Kernel::class]->setArtisan(null);
36 | static::$migrated = true;
37 | }
38 | $this->beginDatabaseTransaction();
39 | }
40 |
41 | public function beginDatabaseTransaction()
42 | {
43 | $database = $this->app->make('db');
44 |
45 | foreach ($this->connectionsToTransact() as $name) {
46 | $database->connection($name)->beginTransaction();
47 | }
48 |
49 | $this->beforeApplicationDestroyed(function () use ($database) {
50 | foreach ($this->connectionsToTransact() as $name) {
51 | $database->connection($name)->rollBack();
52 | }
53 | });
54 | }
55 |
56 | protected function connectionsToTransact()
57 | {
58 | return property_exists($this, 'connectionsToTransact')
59 | ? $this->connectionsToTransact : [null];
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
32 |
33 | $status = $kernel->handle(
34 | $input = new Symfony\Component\Console\Input\ArgvInput,
35 | new Symfony\Component\Console\Output\ConsoleOutput
36 | );
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Shutdown The Application
41 | |--------------------------------------------------------------------------
42 | |
43 | | Once Artisan has finished running. We will fire off the shutdown events
44 | | so that any final work may be done by the application before we shut
45 | | down the process. This is the last thing to happen to the request.
46 | |
47 | */
48 |
49 | $kernel->terminate($input, $status);
50 |
51 | exit($status);
52 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | App\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | App\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/resources/views/partials/navbar.blade.php:
--------------------------------------------------------------------------------
1 |
45 |
--------------------------------------------------------------------------------
/resources/views/team/show.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Team details
8 |
9 | Edit
10 |
11 |
12 |
13 |
14 |
15 | Name
16 |
17 |
18 | {{ $team->name }}
19 |
20 |
21 |
22 | Members
23 | Edit
24 |
25 |
26 |
27 | @foreach ($team->members as $member)
28 | -
29 | @if (Auth::user()->is_admin)
30 |
31 | {{ $member->username }}
32 |
33 | @else
34 | {{ $member->username }}
35 | @endif
36 |
37 | @endforeach
38 |
39 |
40 |
41 |
42 |
43 |
44 | Team Jobs
45 |
46 |
47 | @include('job.partials.index', ['jobs' => $team->jobs])
48 |
49 | @endsection
50 |
--------------------------------------------------------------------------------
/app/Notifications/JobHasGoneAwol.php:
--------------------------------------------------------------------------------
1 | job = $job;
25 | }
26 |
27 | /**
28 | * Get the notification's delivery channels.
29 | *
30 | * @param mixed $notifiable
31 | * @return array
32 | */
33 | public function via($notifiable)
34 | {
35 | return ['mail'];
36 | }
37 |
38 | /**
39 | * Get the mail representation of the notification.
40 | *
41 | * @param mixed $notifiable
42 | * @return \Illuminate\Notifications\Messages\MailMessage
43 | */
44 | public function toMail($notifiable)
45 | {
46 | return (new MailMessage)
47 | ->subject(config('cronmon.email_prefix').' Job has not run')
48 | ->line('Cron job "'.$this->job->name.'" has not run')
49 | ->action('Check the status', route('job.show', $this->job->id))
50 | ->line('Job : '.$this->job->name)
51 | ->line('Last Run : '.$this->job->getLastRun().' ('.$this->job->getLastRunDiff().')')
52 | ->line('Schedule : '.$this->job->getSchedule());
53 | }
54 |
55 | /**
56 | * Get the array representation of the notification.
57 | *
58 | * @param mixed $notifiable
59 | * @return array
60 | */
61 | public function toArray($notifiable)
62 | {
63 | return [
64 | //
65 | ];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/Http/Controllers/CronjobController.php:
--------------------------------------------------------------------------------
1 | authorize('edit-job', $job);
17 |
18 | return view('job.show', compact('job'));
19 | }
20 |
21 | public function index()
22 | {
23 | $jobs = Cronjob::orderBy('name')->with('user', 'team')->get();
24 |
25 | return view('job.admin.index', compact('jobs'));
26 | }
27 |
28 | public function create()
29 | {
30 | $job = Cronjob::newDefault();
31 |
32 | return view('job.create', compact('job'));
33 | }
34 |
35 | public function store(StoreCronjob $request)
36 | {
37 | $request->user()->addNewJob($request->all());
38 |
39 | return redirect()->route('home');
40 | }
41 |
42 | public function edit($id)
43 | {
44 | $job = Cronjob::findOrFail($id);
45 | $this->authorize('edit-job', $job);
46 | $users = User::orderBy('username')->get();
47 |
48 | return view('job.edit', compact('job', 'users'));
49 | }
50 |
51 | public function update(UpdateCronjob $request, $id)
52 | {
53 | $job = Cronjob::findOrFail($id);
54 | $this->authorize('edit-job', $job);
55 | $job->updateFromForm($request->all());
56 |
57 | return redirect()->route('home');
58 | }
59 |
60 | public function destroy($id)
61 | {
62 | $job = Cronjob::findOrFail($id);
63 | $this->authorize('edit-job', $job);
64 | $job->pings()->delete();
65 | $job->delete();
66 |
67 | return redirect()->route('home');
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/Console/Commands/ReformatRoutes.php:
--------------------------------------------------------------------------------
1 | option('file').'.php';
21 | $contents = file_get_contents($fileName);
22 |
23 | $newContents = collect(explode(PHP_EOL, $contents))->map(function ($line) {
24 | if (! str_contains($line, '@')) {
25 | return $line;
26 | }
27 |
28 | $controllerSection = [];
29 | if (preg_match('/, (\"|\')([A-Za-z0-9\\\\]+@[a-zA-Z]+)(\"|\')/', $line, $controllerSection) === 0) {
30 | return $line;
31 | }
32 |
33 | [$controllerName, $methodName] = explode('@', $controllerSection[2]);
34 |
35 | $classPrefix = 'App\\Http\\Controllers\\';
36 | if (str_contains($controllerName, $classPrefix)) {
37 | $classPrefix = '';
38 | }
39 | $newLine = str_replace(
40 | $controllerSection[0],
41 | ", [{$classPrefix}".$controllerName.'::class, '."'{$methodName}']",
42 | $line
43 | );
44 |
45 | return $newLine;
46 | });
47 |
48 | if ($this->option('dry-run')) {
49 | $this->info($newContents->implode(PHP_EOL));
50 |
51 | return;
52 | }
53 |
54 | file_put_contents($fileName, $newContents->implode(PHP_EOL));
55 |
56 | $this->info('Done. Remember to set the $namespace in RouteServiceProvider to null.');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | mapApiRoutes();
39 |
40 | $this->mapWebRoutes();
41 |
42 | //
43 | }
44 |
45 | /**
46 | * Define the "web" routes for the application.
47 | *
48 | * These routes all receive session state, CSRF protection, etc.
49 | *
50 | * @return void
51 | */
52 | protected function mapWebRoutes()
53 | {
54 | Route::group([
55 | 'middleware' => 'web',
56 | 'namespace' => $this->namespace,
57 | ], function ($router) {
58 | require base_path('routes/web.php');
59 | });
60 | }
61 |
62 | /**
63 | * Define the "api" routes for the application.
64 | *
65 | * These routes are typically stateless.
66 | *
67 | * @return void
68 | */
69 | protected function mapApiRoutes()
70 | {
71 | Route::group([
72 | 'middleware' => 'api',
73 | 'namespace' => $this->namespace,
74 | 'prefix' => '',
75 | ], function ($router) {
76 | require base_path('routes/api.php');
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | /*
11 | |--------------------------------------------------------------------------
12 | | Register The Auto Loader
13 | |--------------------------------------------------------------------------
14 | |
15 | | Composer provides a convenient, automatically generated class loader for
16 | | our application. We just need to utilize it! We'll simply require it
17 | | into the script here so that we don't have to worry about manual
18 | | loading any of our classes later on. It feels nice to relax.
19 | |
20 | */
21 |
22 | require __DIR__.'/../bootstrap/autoload.php';
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Turn On The Lights
27 | |--------------------------------------------------------------------------
28 | |
29 | | We need to illuminate PHP development, so let us turn on the lights.
30 | | This bootstraps the framework and gets it ready for use, then it
31 | | will load up this application so that we can run it and send
32 | | the responses back to the browser and delight our users.
33 | |
34 | */
35 |
36 | $app = require_once __DIR__.'/../bootstrap/app.php';
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Run The Application
41 | |--------------------------------------------------------------------------
42 | |
43 | | Once we have the application, we can handle the incoming request
44 | | through the kernel, and send the associated response back to
45 | | the client's browser allowing them to enjoy the creative
46 | | and wonderful application we have prepared for them.
47 | |
48 | */
49 |
50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
51 |
52 | $response = $kernel->handle(
53 | $request = Illuminate\Http\Request::capture()
54 | );
55 |
56 | $response->send();
57 |
58 | $kernel->terminate($request, $response);
59 |
--------------------------------------------------------------------------------
/app/Providers/AuthServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
17 | ];
18 |
19 | /**
20 | * Register any authentication / authorization services.
21 | *
22 | * @return void
23 | */
24 | public function boot()
25 | {
26 | $this->registerPolicies();
27 |
28 | Gate::before(function ($user, $ability) {
29 | if ($user->is_admin) {
30 | return true;
31 | }
32 | });
33 |
34 | Gate::define('update-job', function ($user, $job) {
35 | if ($user->id == $job->user_id) {
36 | return true;
37 | }
38 | if ($user->onTeam($job->team_id)) {
39 | return true;
40 | }
41 |
42 | return false;
43 | });
44 | Gate::define('view-job', function ($user, $job) {
45 | if ($user->id == $job->user_id) {
46 | return true;
47 | }
48 | if ($user->onTeam($job->team_id)) {
49 | return true;
50 | }
51 |
52 | return false;
53 | });
54 | Gate::define('edit-job', function ($user, $job) {
55 | if ($user->id == $job->user_id) {
56 | return true;
57 | }
58 | if ($user->onTeam($job->team_id)) {
59 | return true;
60 | }
61 |
62 | return false;
63 | });
64 | Gate::define('edit-team', function ($user, $team) {
65 | if ($user->onTeam($team)) {
66 | return true;
67 | }
68 |
69 | return false;
70 | });
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
28 | \App\Http\Middleware\EncryptCookies::class,
29 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
30 | \Illuminate\Session\Middleware\StartSession::class,
31 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
32 | \App\Http\Middleware\VerifyCsrfToken::class,
33 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
34 | ],
35 |
36 | 'api' => [
37 | 'throttle:60,1',
38 | 'bindings',
39 | ],
40 | ];
41 |
42 | /**
43 | * The application's route middleware.
44 | *
45 | * These middleware may be assigned to groups or used individually.
46 | *
47 | * @var array
48 | */
49 | protected $routeMiddleware = [
50 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
51 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
52 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
53 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
54 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
55 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
56 | 'admin.only' => \App\Http\Middleware\AdminOnly::class,
57 | ];
58 | }
59 |
--------------------------------------------------------------------------------
/app/Http/Controllers/UserController.php:
--------------------------------------------------------------------------------
1 | get();
14 |
15 | return view('user.index', compact('users'));
16 | }
17 |
18 | public function show($id)
19 | {
20 | $user = User::findOrFail($id);
21 |
22 | return view('user.show', compact('user'));
23 | }
24 |
25 | public function create()
26 | {
27 | $user = new User;
28 |
29 | return view('user.create', compact('user'));
30 | }
31 |
32 | public function store(Request $request)
33 | {
34 | $this->validate($request, [
35 | 'username' => 'required|unique:users',
36 | 'email' => 'required|email|unique:users',
37 | 'is_admin' => 'boolean',
38 | ]);
39 | User::register($request->all());
40 |
41 | return redirect()->route('user.index');
42 | }
43 |
44 | public function edit($id)
45 | {
46 | $user = User::findOrFail($id);
47 |
48 | return view('user.edit', compact('user'));
49 | }
50 |
51 | public function update($id, Request $request)
52 | {
53 | $this->validate($request, [
54 | 'username' => ['required', 'max:255', Rule::unique('users')->ignore($id)],
55 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($id)],
56 | 'is_admin' => 'boolean',
57 | ]);
58 | $user = User::findOrFail($id);
59 | $user->fill($request->all());
60 | $user->save();
61 | if ($request->has('reset_password')) {
62 | $user->sendResetLink();
63 | }
64 |
65 | return redirect()->route('user.index');
66 | }
67 |
68 | public function destroy($id)
69 | {
70 | $user = User::findOrFail($id);
71 | $user->removeFromSystem();
72 |
73 | return redirect()->route('user.index');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Feature/AutoCreateAdminTest.php:
--------------------------------------------------------------------------------
1 | 'jenny']);
20 | config(['cronmon.admin_email' => 'jenny@example.com']);
21 | config(['cronmon.admin_password' => 'secret']);
22 |
23 | Artisan::call('cronmon:autocreateadmin');
24 |
25 | tap(User::first(), function ($user) {
26 | $this->assertEquals('jenny', $user->username);
27 | $this->assertEquals('jenny@example.com', $user->email);
28 | $this->assertTrue(Hash::check('secret', $user->password));
29 | });
30 | }
31 |
32 | /** @test */
33 | public function we_can_automatically_create_an_admin_user_automatically_via_the_content_of_files()
34 | {
35 | $usernameFile = tempnam(sys_get_temp_dir(), 'prefixx');
36 | file_put_contents($usernameFile, 'jackie');
37 | $emailFile = tempnam(sys_get_temp_dir(), 'prefixx');
38 | file_put_contents($emailFile, 'jackie@example.com');
39 | $passwordFile = tempnam(sys_get_temp_dir(), 'prefixx');
40 | file_put_contents($passwordFile, 'password1');
41 |
42 | config(['cronmon.admin_username_file' => $usernameFile]);
43 | config(['cronmon.admin_email_file' => $emailFile]);
44 | config(['cronmon.admin_password_file' => $passwordFile]);
45 |
46 | Artisan::call('cronmon:autocreateadmin');
47 |
48 | tap(User::first(), function ($user) {
49 | $this->assertEquals('jackie', $user->username);
50 | $this->assertEquals('jackie@example.com', $user->email);
51 | $this->assertTrue(Hash::check('password1', $user->password));
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "type": "project",
4 | "description": "The Laravel Framework.",
5 | "keywords": [
6 | "framework",
7 | "laravel"
8 | ],
9 | "license": "MIT",
10 | "require": {
11 | "php": "^7.3",
12 | "doctrine/dbal": "^2.9",
13 | "fideloper/proxy": "^4.0",
14 | "guzzlehttp/guzzle": "^7.0.1",
15 | "laravel/framework": "^8.0",
16 | "laravel/horizon": "^5.0",
17 | "laravel/tinker": "^2.0",
18 | "laravel/ui": "^3.0",
19 | "livewire/livewire": "2.5.2",
20 | "ramsey/uuid": "^4.0"
21 | },
22 | "require-dev": {
23 | "facade/ignition": "^2.3.6",
24 | "fzaninotto/faker": "^1.4",
25 | "mockery/mockery": "^1.0",
26 | "nunomaduro/collision": "^5.0",
27 | "phpunit/phpunit": "^9.0",
28 | "laravel/dusk": "^6.0",
29 | "blastcloud/guzzler": "^1.5",
30 | "laravel/browser-kit-testing": "^6.0"
31 | },
32 | "config": {
33 | "optimize-autoloader": true,
34 | "preferred-install": "dist",
35 | "sort-packages": true
36 | },
37 | "extra": {
38 | "laravel": {
39 | "dont-discover": []
40 | }
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "App\\": "app/",
45 | "Database\\Factories\\": "database/factories/",
46 | "Database\\Seeders\\": "database/seeders/" },
47 | "classmap": [
48 | ]
49 | },
50 | "autoload-dev": {
51 | "psr-4": {
52 | "Tests\\": "tests/"
53 | }
54 | },
55 | "minimum-stability": "dev",
56 | "prefer-stable": true,
57 | "scripts": {
58 | "post-autoload-dump": [
59 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
60 | "@php artisan package:discover --ansi"
61 | ],
62 | "post-root-package-install": [
63 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
64 | ],
65 | "post-create-project-cmd": [
66 | "@php artisan key:generate --ansi"
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
40 | }
41 |
42 | /**
43 | * Get a validator for an incoming registration request.
44 | *
45 | * @param array $data
46 | * @return \Illuminate\Contracts\Validation\Validator
47 | */
48 | protected function validator(array $data)
49 | {
50 | return Validator::make($data, [
51 | 'name' => 'required|max:255',
52 | 'email' => 'required|email|max:255|unique:users',
53 | 'password' => 'required|min:6|confirmed',
54 | ]);
55 | }
56 |
57 | /**
58 | * Create a new user instance after a valid registration.
59 | *
60 | * @param array $data
61 | * @return User
62 | */
63 | protected function create(array $data)
64 | {
65 | abort(403); // hack to disable registrations
66 |
67 | return User::create([
68 | 'name' => $data['name'],
69 | 'email' => $data['email'],
70 | 'password' => bcrypt($data['password']),
71 | ]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | 'local',
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Cloud Filesystem Disk
21 | |--------------------------------------------------------------------------
22 | |
23 | | Many applications store files both locally and in the cloud. For this
24 | | reason, you may specify a default "cloud" driver here. This driver
25 | | will be bound as the Cloud disk implementation in the container.
26 | |
27 | */
28 |
29 | 'cloud' => 's3',
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Filesystem Disks
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here you may configure as many filesystem "disks" as you wish, and you
37 | | may even configure multiple disks of the same driver. Defaults have
38 | | been setup for each driver as an example of the required options.
39 | |
40 | | Supported Drivers: "local", "ftp", "s3", "rackspace"
41 | |
42 | */
43 |
44 | 'disks' => [
45 |
46 | 'local' => [
47 | 'driver' => 'local',
48 | 'root' => storage_path('app'),
49 | ],
50 |
51 | 'public' => [
52 | 'driver' => 'local',
53 | 'root' => storage_path('app/public'),
54 | 'visibility' => 'public',
55 | ],
56 |
57 | 's3' => [
58 | 'driver' => 's3',
59 | 'key' => 'your-key',
60 | 'secret' => 'your-secret',
61 | 'region' => 'your-region',
62 | 'bucket' => 'your-bucket',
63 | ],
64 |
65 | ],
66 |
67 | ];
68 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | x-env:
4 | environment: &default-env
5 | MAIL_DRIVER: smtp
6 | MAIL_HOST: mailhog
7 | MAIL_PORT: 1025
8 | REDIS_HOST: redis
9 | QUEUE_CONNECTION: redis
10 | SESSION_DRIVER: redis
11 | DB_CONNECTION: mysql
12 | DB_HOST: mysql
13 | DB_PORT: 3306
14 | DB_DATABASE: homestead
15 | DB_USERNAME: homestead
16 | DB_PASSWORD: secret
17 | BROADCAST_DRIVER: log
18 | CACHE_DRIVER: redis
19 | SESSION_DRIVER: redis
20 | QUEUE_DRIVER: redis
21 | APP_ENV: production
22 | APP_KEY: base64:WfpY+XDjPbQKYb9VDP7zyP4G7WBuB9rLHswC34DsNoc=
23 | APP_DEBUG: 1
24 | APP_LOG_LEVEL: debug
25 | APP_URL: http://localhost:${APP_PORT:-3000}
26 | BROADCAST_DRIVER: log
27 | CACHE_DRIVER: file
28 | SESSION_DRIVER: file
29 | QUEUE_DRIVER: file
30 | MAIL_FROM_ADDRESS: cronmon@example.org
31 | MAIL_FROM_NAME: Cronmon
32 | CRONMON_ADMIN_USERNAME: ${CRONMON_ADMIN_USERNAME}
33 | CRONMON_ADMIN_EMAIL: ${CRONMON_ADMIN_EMAIL}
34 | CRONMON_ADMIN_PASSWORD: ${CRONMON_ADMIN_PASSWORD}
35 |
36 | services:
37 | app:
38 | image: ohffs/cronmon:2.0.4
39 | environment:
40 | CONTAINER_ROLE: app
41 | <<: *default-env
42 | ports:
43 | - "${APP_PORT:-3000}:80"
44 | depends_on:
45 | - redis
46 | - mysql
47 | - mailhog
48 |
49 | scheduler:
50 | image: ohffs/cronmon:2.0.4
51 | environment:
52 | CONTAINER_ROLE: scheduler
53 | <<: *default-env
54 | depends_on:
55 | - app
56 |
57 | queue:
58 | image: ohffs/cronmon:2.0.4
59 | environment:
60 | CONTAINER_ROLE: queue
61 | <<: *default-env
62 | depends_on:
63 | - app
64 |
65 | migrations:
66 | image: ohffs/cronmon:2.0.4
67 | environment:
68 | CONTAINER_ROLE: migrations
69 | <<: *default-env
70 | depends_on:
71 | - app
72 |
73 | redis:
74 | image: redis:5.0.4
75 | volumes:
76 | - redis:/data
77 |
78 | mysql:
79 | image: mysql:5.7
80 | volumes:
81 | - mysql:/var/lib/mysql
82 | environment:
83 | MYSQL_DATABASE: homestead
84 | MYSQL_ROOT_PASSWORD: root
85 | MYSQL_USER: homestead
86 | MYSQL_PASSWORD: secret
87 |
88 | mailhog:
89 | image: mailhog/mailhog
90 | ports:
91 | - "3025:8025"
92 |
93 | volumes:
94 | redis:
95 | driver: "local"
96 | mysql:
97 | driver: "local"
98 |
99 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | expectsJson()) {
61 | return response()->json(['error' => 'Unauthenticated.'], 401);
62 | }
63 |
64 | return redirect()->guest('login');
65 | }
66 |
67 | protected function whoopsHandler()
68 | {
69 | try {
70 | return app(\Whoops\Handler\HandlerInterface::class);
71 | } catch (\Illuminate\Contracts\Container\BindingResolutionException $e) {
72 | return (new \Illuminate\Foundation\Exceptions\WhoopsHandler)->forDebug();
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/Policies/TemplatePolicy.php:
--------------------------------------------------------------------------------
1 | is_admin;
22 | }
23 |
24 | /**
25 | * Determine whether the user can view the template.
26 | *
27 | * @param \App\Models\User $user
28 | * @param \App\Models\Template $template
29 | * @return mixed
30 | */
31 | public function view(User $user, Template $template)
32 | {
33 | return $template->user_id == $user->id;
34 | }
35 |
36 | /**
37 | * Determine whether the user can create templates.
38 | *
39 | * @param \App\Models\User $user
40 | * @return mixed
41 | */
42 | public function create(User $user)
43 | {
44 | return true;
45 | }
46 |
47 | /**
48 | * Determine whether the user can update the template.
49 | *
50 | * @param \App\Models\User $user
51 | * @param \App\Models\Template $template
52 | * @return mixed
53 | */
54 | public function update(User $user, Template $template)
55 | {
56 | return $template->user_id == $user->id;
57 | }
58 |
59 | /**
60 | * Determine whether the user can delete the template.
61 | *
62 | * @param \App\Models\User $user
63 | * @param \App\Models\Template $template
64 | * @return mixed
65 | */
66 | public function delete(User $user, Template $template)
67 | {
68 | return $template->user_id == $user->id;
69 | }
70 |
71 | /**
72 | * Determine whether the user can restore the template.
73 | *
74 | * @param \App\Models\User $user
75 | * @param \App\Models\Template $template
76 | * @return mixed
77 | */
78 | public function restore(User $user, Template $template)
79 | {
80 | //
81 | }
82 |
83 | /**
84 | * Determine whether the user can permanently delete the template.
85 | *
86 | * @param \App\Models\User $user
87 | * @param \App\Models\Template $template
88 | * @return mixed
89 | */
90 | public function forceDelete(User $user, Template $template)
91 | {
92 | //
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Api/CronjobController.php:
--------------------------------------------------------------------------------
1 | firstOrFail();
17 |
18 | return response()->json([
19 | 'data' => $job->toArray(),
20 | ]);
21 | }
22 |
23 | public function update(Request $request)
24 | {
25 | $request->validate([
26 | 'api_key' => 'required',
27 | 'schedule' => ['required_without_all:period,period_units', new ValidCronExpression],
28 | 'name' => 'required',
29 | 'team' => 'nullable|string|exists:teams,name',
30 | 'grace' => 'nullable|numeric',
31 | 'grace_units' => 'nullable|in:minute,hour,day,week',
32 | 'period' => 'required_without:schedule|numeric',
33 | 'period_units' => 'required_without:schedule|in:minute,hour,day,week',
34 | ]);
35 |
36 | $user = User::where('api_key', '=', $request->api_key)->firstOrFail();
37 | $team = false;
38 | if ($request->filled('team')) {
39 | $team = $user->teams()->where('name', '=', $request->team)->firstOrFail();
40 | }
41 |
42 | $job = Cronjob::where('name', '=', $request->name)->first();
43 | if (! $job) {
44 | $job = $user->addNewJob([
45 | 'cron_schedule' => $request->schedule,
46 | 'name' => $request->name,
47 | 'team_id' => $team ? $team->id : -1,
48 | 'grace' => $request->grace ?? 1,
49 | 'grace_units' => $request->grace_units ?? 'hour',
50 | 'period' => $request->period ?? 1,
51 | 'period_units' => $request->period_units ?? 'hour',
52 | ]);
53 | } else {
54 | $job = $job->updateFromForm([
55 | 'cron_schedule' => $request->schedule,
56 | 'team_id' => $team ? $team->id : $job->team_id,
57 | 'grace' => $request->grace ?? 1,
58 | 'grace_units' => $request->grace_units ?? 'hour',
59 | 'period' => $request->period ?? 1,
60 | 'period_units' => $request->period_units ?? 'hour',
61 | ]);
62 | }
63 |
64 | return response()->json([
65 | 'job' => $job->toArray(),
66 | ]);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docker-compose-demo.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | image: cronmon
5 | container_name: cronmon
6 | build:
7 | context: .
8 | dockerfile: docker/Dockerfile
9 | depends_on:
10 | - redis
11 | - mysql
12 | networks:
13 | - private
14 | expose:
15 | - "80"
16 | ports:
17 | - "8002:80"
18 | environment:
19 | APP_ENV: local
20 | CONTAINER_ROLE: app
21 | DB_CONNECTION: mysql
22 | DB_HOST: mysql
23 | DB_DATABASE: cronmon
24 | DB_USERNAME: root
25 | DB_PASSWORD: secret
26 | CACHE_DRIVER: redis
27 | SESSION_DRIVER: redis
28 | QUEUE_DRIVER: redis
29 | REDIS_HOST: redis
30 | MAIL_HOST: mailhog
31 | DEFAULT_DISK: images
32 |
33 | scheduler:
34 | image: cronmon
35 | container_name: cronmon-scheduler
36 | depends_on:
37 | - app
38 | networks:
39 | - private
40 | environment:
41 | APP_ENV: local
42 | CONTAINER_ROLE: scheduler
43 | DB_CONNECTION: mysql
44 | DB_HOST: mysql
45 | DB_DATABASE: cronmon
46 | DB_USERNAME: root
47 | DB_PASSWORD: secret
48 | CACHE_DRIVER: redis
49 | SESSION_DRIVER: redis
50 | QUEUE_DRIVER: redis
51 | REDIS_HOST: redis
52 | MAIL_HOST: mailhog
53 |
54 | queue:
55 | image: cronmon
56 | container_name: cronmon-queue
57 | depends_on:
58 | - app
59 | networks:
60 | - private
61 | environment:
62 | APP_ENV: local
63 | CONTAINER_ROLE: queue
64 | DB_CONNECTION: mysql
65 | DB_HOST: mysql
66 | DB_DATABASE: cronmon
67 | DB_USERNAME: root
68 | DB_PASSWORD: secret
69 | CACHE_DRIVER: redis
70 | SESSION_DRIVER: redis
71 | QUEUE_DRIVER: redis
72 | REDIS_HOST: redis
73 | MAIL_HOST: mailhog
74 |
75 | redis:
76 | container_name: cronmon-redis
77 | image: redis:4
78 | networks:
79 | - private
80 | volumes:
81 | - redis:/data
82 |
83 | mysql:
84 | container_name: cronmon-mysql
85 | image: mysql:5.7
86 | networks:
87 | - private
88 | volumes:
89 | - mysql:/var/lib/mysql
90 | environment:
91 | MYSQL_DATABASE: cronmon
92 | MYSQL_ROOT_PASSWORD: secret
93 | MYSQL_USER: cronmon
94 | MYSQL_PASSWORD: secret
95 |
96 | mailhog:
97 | container_name: cronmon-mailhog
98 | image: mailhog/mailhog
99 | ports:
100 | - 18025:8025
101 | expose:
102 | - "18025"
103 | networks:
104 | - private
105 |
106 | volumes:
107 | redis:
108 | driver: "local"
109 | mysql:
110 | driver: "local"
111 |
112 | networks:
113 | private:
114 |
--------------------------------------------------------------------------------
/app/Console/Commands/CronmonDiscover.php:
--------------------------------------------------------------------------------
1 | client = $client;
42 | }
43 |
44 | /**
45 | * Execute the console command.
46 | *
47 | * @return mixed
48 | */
49 | public function handle()
50 | {
51 | app()->make(\Illuminate\Contracts\Console\Kernel::class);
52 | $schedule = app()->make(\Illuminate\Console\Scheduling\Schedule::class);
53 |
54 | $responses = collect($schedule->events())->map(function ($event) {
55 | $cron = CronExpression::factory($event->expression);
56 | $date = Carbon::now();
57 | if ($event->timezone) {
58 | $date->setTimezone($event->timezone);
59 | }
60 |
61 | return (object) [
62 | 'expression' => $event->expression,
63 | 'name' => config('app.name').' '.Str::after($event->command, '\'artisan\' '),
64 | ];
65 | })->map(function ($event) {
66 | try {
67 | $response = $this->client->post(
68 | $this->argument('api_url'),
69 | [
70 | \GuzzleHttp\RequestOptions::JSON => [
71 | 'schedule' => $event->expression,
72 | 'name' => $event->name,
73 | 'api_key' => $this->argument('api_key'),
74 | ],
75 | ]
76 | );
77 | } catch (\GuzzleHttp\Exception\BadResponseException $e) {
78 | $response = $e->getResponse();
79 |
80 | return '"'.$event->name.'" Failed : '.$response->getReasonPhrase();
81 | }
82 |
83 | return '"'.$event->name.'" Success';
84 | });
85 |
86 | $responses->each(function ($response) {
87 | $this->line($response);
88 | });
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/resources/views/team/member/edit.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 | Edit {{ $team->name }} members
8 |
9 |
10 |
60 |
61 | @endsection
62 |
--------------------------------------------------------------------------------
/config/livewire.php:
--------------------------------------------------------------------------------
1 | 'App\\Http\\Livewire',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This value sets the path for Livewire component views. This effects
26 | | File manipulation helper commands like `artisan make:livewire`
27 | |
28 | */
29 |
30 | 'view_path' => resource_path('views/livewire'),
31 |
32 | /*
33 | |--------------------------------------------------------------------------
34 | | Livewire Assets URL
35 | |--------------------------------------------------------------------------
36 | |
37 | | This value sets the path to Livewire JavaScript assets, for cases where
38 | | your app's domain root is not the correct path. By default, Livewire
39 | | will load its JavaScript assets from the app's "relative root".
40 | |
41 | | Examples: "/assets", "myurl.com/app"
42 | |
43 | */
44 |
45 | 'asset_url' => env('LIVEWIRE_ASSET_URL', null),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Livewire Endpoint Middleware Group
50 | |--------------------------------------------------------------------------
51 | |
52 | | This value sets the middleware group that will be applied to the main
53 | | Livewire "message" endpoint (the endpoint that gets hit everytime,
54 | | a Livewire component updates). It is set to "web" by default.
55 | |
56 | */
57 |
58 | 'middleware_group' => 'web',
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Manifest File Path
63 | |--------------------------------------------------------------------------
64 | |
65 | | This value sets the path to Livewire manifest file path.
66 | | The default should work for most cases (which is
67 | | "/bootstrap/cache/livewire-components.php)", but for specific
68 | | cases like when hosting on Laravel Vapor, it could be set to a different value.
69 | |
70 | | Example: For Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php"
71 | |
72 | */
73 |
74 | 'manifest_path' => null,
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/app/Console/Commands/AutoCreateAdmin.php:
--------------------------------------------------------------------------------
1 | createAdmin();
44 | } else {
45 | $this->info('No autocreated admin');
46 | }
47 | }
48 |
49 | protected function createAdmin()
50 | {
51 | $email = $this->findValueFor('email');
52 | $username = $this->findValueFor('username');
53 | $password = $this->findValueFor('password');
54 |
55 | $email = trim(strtolower($email));
56 | $admin = User::where('email', '=', $email)->first();
57 | if (! $admin) {
58 | $admin = User::createNewAdmin($username, $email, $password);
59 | $this->info('Auto-created new admin');
60 |
61 | return;
62 | }
63 |
64 | $validator = Validator::make(['email' => $email, 'password' => $password, 'username' => $username], [
65 | 'email' => 'required|email|max:255|unique:users,email,'.$admin->id.',id',
66 | 'password' => 'required|min:8',
67 | 'username' => 'required|unique:users,email,'.$admin->id.',id',
68 | ]);
69 | if ($validator->fails()) {
70 | foreach ($validator->errors()->all() as $error) {
71 | $this->error($error);
72 | }
73 | throw new \RuntimeException('Aborting');
74 | }
75 |
76 | $admin->update([
77 | 'email' => $email,
78 | 'is_admin' => true,
79 | 'username' => $username,
80 | 'password' => bcrypt($password),
81 | ]);
82 |
83 | $this->info('Auto-updated admin user');
84 | }
85 |
86 | public function findValueFor(string $key)
87 | {
88 | if (config("cronmon.admin_{$key}")) {
89 | return config("cronmon.admin_{$key}");
90 | }
91 | if (! config("cronmon.admin_{$key}_file")) {
92 | return null;
93 | }
94 |
95 | return file_get_contents(config("cronmon.admin_{$key}_file"));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
59 | ],
60 |
61 | 'redis' => [
62 | 'driver' => 'redis',
63 | 'connection' => 'default',
64 | 'queue' => env('REDIS_QUEUE', 'default'),
65 | 'retry_after' => 90,
66 | 'block_for' => null,
67 | ],
68 |
69 | ],
70 |
71 | /*
72 | |--------------------------------------------------------------------------
73 | | Failed Queue Jobs
74 | |--------------------------------------------------------------------------
75 | |
76 | | These options configure the behavior of failed queue job logging so you
77 | | can control which database and table are used to store the jobs that
78 | | have failed. You may change them to any database / table you wish.
79 | |
80 | */
81 |
82 | 'failed' => [
83 | 'database' => env('DB_CONNECTION', 'mysql'),
84 | 'table' => 'failed_jobs',
85 | ],
86 |
87 | ];
88 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Log Channels
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may configure the log channels for your application. Out of
27 | | the box, Laravel uses the Monolog PHP logging library. This gives
28 | | you a variety of powerful log handlers / formatters to utilize.
29 | |
30 | | Available Drivers: "single", "daily", "slack", "syslog",
31 | | "errorlog", "monolog",
32 | | "custom", "stack"
33 | |
34 | */
35 |
36 | 'channels' => [
37 | 'stack' => [
38 | 'driver' => 'stack',
39 | 'channels' => ['daily'],
40 | 'ignore_exceptions' => false,
41 | ],
42 |
43 | 'single' => [
44 | 'driver' => 'single',
45 | 'path' => storage_path('logs/laravel.log'),
46 | 'level' => 'debug',
47 | ],
48 |
49 | 'daily' => [
50 | 'driver' => 'daily',
51 | 'path' => storage_path('logs/laravel.log'),
52 | 'level' => 'debug',
53 | 'days' => 14,
54 | ],
55 |
56 | 'slack' => [
57 | 'driver' => 'slack',
58 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
59 | 'username' => 'Laravel Log',
60 | 'emoji' => ':boom:',
61 | 'level' => 'critical',
62 | ],
63 |
64 | 'papertrail' => [
65 | 'driver' => 'monolog',
66 | 'level' => 'debug',
67 | 'handler' => SyslogUdpHandler::class,
68 | 'handler_with' => [
69 | 'host' => env('PAPERTRAIL_URL'),
70 | 'port' => env('PAPERTRAIL_PORT'),
71 | ],
72 | ],
73 |
74 | 'stderr' => [
75 | 'driver' => 'monolog',
76 | 'handler' => StreamHandler::class,
77 | 'formatter' => env('LOG_STDERR_FORMATTER'),
78 | 'with' => [
79 | 'stream' => 'php://stderr',
80 | ],
81 | ],
82 |
83 | 'syslog' => [
84 | 'driver' => 'syslog',
85 | 'level' => 'debug',
86 | ],
87 |
88 | 'errorlog' => [
89 | 'driver' => 'errorlog',
90 | 'level' => 'debug',
91 | ],
92 | ],
93 |
94 | ];
95 |
--------------------------------------------------------------------------------
/resources/views/welcome.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Laravel
9 |
10 |
11 |
12 |
13 |
14 |
66 |
67 |
68 |
69 | @if (Route::has('login'))
70 |
71 | @if (Auth::check())
72 |
Home
73 | @else
74 |
Login
75 |
Register
76 | @endif
77 |
78 | @endif
79 |
80 |
81 |
82 | Laravel
83 |
84 |
85 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/public/vendor/horizon/img/horizon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------