diff --git a/app/assets/js/app.js b/app/assets/js/app.js index c8e4a249..d284d892 100644 --- a/app/assets/js/app.js +++ b/app/assets/js/app.js @@ -109,6 +109,23 @@ $(function() { } }); + // Date picker. + $('input[rel=datepicker]').datetimepicker({ + format: "DD/MM/YYYY HH:mm", + minDate: new Date(), // Don't allow dates before today. + sideBySide: true, + icons: { + time: 'ion-clock', + date: 'ion-android-calendar', + up: 'ion-ios-arrow-up', + down: 'ion-ios-arrow-down', + previous: 'ion-ios-arrow-left', + next: 'ion-ios-arrow-right', + today: 'ion-android-home', + clear: 'ion-trash-a', + } + }); + // Sortable components. var componentList = document.getElementById("component-list"); if (componentList) { @@ -171,7 +188,7 @@ $(function() { }, url: '/dashboard/api/incidents/templates', success: function(tpl) { - var $form = $('form[name=IncidentForm]'); + var $form = $('form[role=form]'); $form.find('input[name=incident\\[name\\]]').val(tpl.name); $form.find('textarea[name=incident\\[message\\]]').val(tpl.template); }, diff --git a/app/assets/sass/_status-page.scss b/app/assets/sass/_status-page.scss index bdfba8d0..0a5b24b1 100644 --- a/app/assets/sass/_status-page.scss +++ b/app/assets/sass/_status-page.scss @@ -144,8 +144,12 @@ body.status-page { top: 14px; .icon { position: absolute; + &.ion-android-calendar { + top: 7px; + left: 11px; + } &.ion-flag { - top: 10px; + top: 7px; left: 13px; } &.ion-alert { @@ -157,10 +161,13 @@ body.status-page { left: 10px; } &.ion-checkmark { - top: 10px; + top: 7px; left: 11px; } } + &.status-0 { + color: $cachet_pink; + } &.status-1 { color: $cachet_orange; } diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index 73105dbc..0aa5dd16 100644 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -32,6 +32,7 @@ html, body { // Styles for plugins @import "plugins/messenger"; @import "plugins/animate"; +@import "plugins/bootstrap-datetimepicker/bootstrap-datetimepicker"; // Status Page will need to override certain styles. @import "status-page"; diff --git a/app/assets/sass/plugins/bootstrap-datetimepicker/bootstrap-datetimepicker.scss b/app/assets/sass/plugins/bootstrap-datetimepicker/bootstrap-datetimepicker.scss new file mode 100644 index 00000000..5def8f23 --- /dev/null +++ b/app/assets/sass/plugins/bootstrap-datetimepicker/bootstrap-datetimepicker.scss @@ -0,0 +1,301 @@ +// Import boostrap variables including default color palette and fonts +@import "../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables"; + +.bootstrap-datetimepicker-widget { + top: 0; + left: 0; + width: 250px; + padding: 4px; + margin-top: 1px; + z-index: 99999 !important; + border-radius: $border-radius-base; + + &.timepicker-sbs { + width: 600px; + } + + &.bottom { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0,0,0,.2); + position: absolute; + top: -7px; + left: 7px; + } + + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + position: absolute; + top: -6px; + left: 8px; + } + } + + &.top { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #ccc; + border-top-color: rgba(0,0,0,.2); + position: absolute; + bottom: -7px; + left: 6px; + } + + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + position: absolute; + bottom: -6px; + left: 7px; + } + } + + & .dow { + width: 14.2857%; + } + + &.pull-right { + &:before { + left: auto; + right: 6px; + } + + &:after { + left: auto; + right: 7px; + } + } + + >ul { + list-style-type: none; + margin: 0; + } + + a[data-action] { + padding: 6px 0; + } + + a[data-action]:active { + box-shadow: none; + } + + .timepicker-hour, .timepicker-minute, .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; + } + + button[data-action] { + padding: 6px; + } + + table[data-hour-format="12"] .separator { + width: 4px; + padding: 0; + margin: 0; + } + + .datepicker > div { + display: none; + } + + .picker-switch { + text-align: center; + } + + table { + width: 100%; + margin: 0; + } + + td, + th { + text-align: center; + border-radius: $border-radius-base; + } + + td { + height: 54px; + line-height: 54px; + width: 54px; + + &.cw { + font-size: 10px; + height: 20px; + line-height: 20px; + color: $gray-light; + } + + &.day { + height: 20px; + line-height: 20px; + width: 20px; + } + + &.day:hover, + &.hour:hover, + &.minute:hover, + &.second:hover { + background: $gray-lighter; + cursor: pointer; + } + + &.old, + &.new { + color: $gray-light; + } + + &.today { + position: relative; + + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-bottom: 7px solid $btn-primary-bg; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; + } + } + + &.active, + &.active:hover { + background-color: $btn-primary-bg; + color: $btn-primary-color; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } + + &.active.today:before { + border-bottom-color: #fff; + } + + &.disabled, + &.disabled:hover { + background: none; + color: $gray-light; + cursor: not-allowed; + } + + span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: $border-radius-base; + + &:hover { + background: $gray-lighter; + } + + &.active { + background-color: $btn-primary-bg; + color: $btn-primary-color; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } + + &.old { + color: $gray-light; + } + + &.disabled, + &.disabled:hover { + background: none; + color: $gray-light; + cursor: not-allowed; + } + } + } + + th { + height: 20px; + line-height: 20px; + width: 20px; + + &.picker-switch { + width: 145px; + } + + &.next, + &.prev { + font-size: $font-size-base * 1.5; + } + + &.disabled, + &.disabled:hover { + background: none; + color: $gray-light; + cursor: not-allowed; + } + } + + thead tr:first-child th { + cursor: pointer; + + &:hover { + background: $gray-lighter; + } + } +} + +.input-group { + &.date { + .input-group-addon span { + display: block; + cursor: pointer; + width: 16px; + height: 16px; + } + } +} + +.bootstrap-datetimepicker-widget.left-oriented { + &:before { + left: auto; + right: 6px; + } + + &:after { + left: auto; + right: 7px; + } +} + +.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody > tr > td { + padding: 0px !important; +} + +@media screen and (max-width: 767px) { + .bootstrap-datetimepicker-widget.timepicker-sbs { + width: 283px; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} diff --git a/app/database/migrations/2015_01_05_202609_CreateIncidentsTable.php b/app/database/migrations/2015_01_05_202609_CreateIncidentsTable.php index 0b6a0542..df9d910c 100644 --- a/app/database/migrations/2015_01_05_202609_CreateIncidentsTable.php +++ b/app/database/migrations/2015_01_05_202609_CreateIncidentsTable.php @@ -20,6 +20,7 @@ class CreateIncidentsTable extends Migration $table->integer('status'); $table->longText('message'); $table->integer('user_id'); + $table->timestamp('scheduled_at'); $table->timestamps(); $table->softDeletes(); diff --git a/app/lang/en/cachet.php b/app/lang/en/cachet.php index 60e02500..9acafc25 100644 --- a/app/lang/en/cachet.php +++ b/app/lang/en/cachet.php @@ -18,7 +18,10 @@ return [ 'previous_week' => 'Previous week', 'next_week' => 'Next week', 'none' => 'Nothing to report', + 'scheduled' => 'Scheduled Maintenance', + 'scheduled_at' => ', scheduled :timestamp', 'status' => [ + 0 => 'Scheduled', // TODO: Hopefully remove this. 1 => 'Investigating', 2 => 'Identified', 3 => 'Watching', diff --git a/app/lang/en/dashboard.php b/app/lang/en/dashboard.php index 614a71e3..b6a0e3d8 100644 --- a/app/lang/en/dashboard.php +++ b/app/lang/en/dashboard.php @@ -6,6 +6,7 @@ return [ // Incidents 'incidents' => [ + 'title' => 'Incidents & Schedule', 'incidents' => 'Incidents', 'logged' => '{0} There are no incidents, good work.|You have logged one incident.|You have reported :count incidents.', 'incident-create-template' => 'Create Template', @@ -37,6 +38,26 @@ return [ ], ], + // Incident Maintenance + 'schedule' => [ + 'schedule' => 'Scheduled Maintenance', + 'scheduled_at' => 'Scheduled at :timestamp', + 'add' => [ + 'title' => 'Add Scheduled Maintenance', + 'success' => 'Schedule added.', + 'failure' => 'Something went wrong adding the schedule.', + ], + 'edit' => [ + 'title' => 'Edit Scheduled Maintenance', + 'success' => 'Schedule has been updated!', + 'failure' => 'Something went wrong editing the schedule.', + ], + 'delete' => [ + 'success' => 'The schedule has been deleted and will not show on your status page.', + 'failure' => 'The schedule could not be deleted. Please try again.', + ], + ], + // Components 'components' => [ 'components' => 'Components', diff --git a/app/lang/en/forms.php b/app/lang/en/forms.php index 4ed6449d..d2f79ee2 100644 --- a/app/lang/en/forms.php +++ b/app/lang/en/forms.php @@ -30,6 +30,7 @@ return [ 'component' => 'Component', 'message' => 'Message', 'message-help' => 'You may also use Markdown.', + 'scheduled_at' => 'When to schedule the maintenance for?', 'templates' => [ 'name' => 'Name', diff --git a/app/routes/dashboard.php b/app/routes/dashboard.php index 3e68737f..fab72b22 100644 --- a/app/routes/dashboard.php +++ b/app/routes/dashboard.php @@ -1,6 +1,10 @@ 'auth', 'prefix' => 'dashboard', 'namespace' => 'CachetHQ\Cachet\Http\Controllers'], function () { +Route::group([ + 'before' => 'auth', + 'prefix' => 'dashboard', + 'namespace' => 'CachetHQ\Cachet\Http\Controllers', +], function () { // Dashboard Route::get('/', [ 'as' => 'dashboard', @@ -55,6 +59,28 @@ Route::group(['before' => 'auth', 'prefix' => 'dashboard', 'namespace' => 'Cache Route::post('{incident}/edit', 'DashIncidentController@editIncidentAction'); }); + // Scheduled Maintenance + Route::group(['prefix' => 'schedule'], function () { + Route::get('/', ['as' => 'dashboard.schedule', 'uses' => 'DashScheduleController@showIndex']); + + Route::get('add', [ + 'as' => 'dashboard.schedule.add', + 'uses' => 'DashScheduleController@showAddSchedule', + ]); + Route::post('add', 'DashScheduleController@addScheduleAction'); + + Route::get('{incident}/edit', [ + 'as' => 'dashboard.schedule.edit', + 'uses' => 'DashScheduleController@showEditSchedule', + ]); + Route::post('{incident}/edit', 'DashScheduleController@editScheduleAction'); + + Route::delete('{incident}/delete', [ + 'as' => 'dashboard.schedule.delete', + 'uses' => 'DashScheduleController@deleteScheduleAction', + ]); + }); + // Incident Templates Route::group(['prefix' => 'templates'], function () { Route::get('/', [ diff --git a/app/views/dashboard/incidents/add.blade.php b/app/views/dashboard/incidents/add.blade.php index 03ca1e38..44a445c6 100644 --- a/app/views/dashboard/incidents/add.blade.php +++ b/app/views/dashboard/incidents/add.blade.php @@ -14,7 +14,7 @@