Merge pull request #929 from cachethq/improved-theming

Theming of Cachet is much simpler with more color settings
This commit is contained in:
Joe Cohen 2015-09-04 17:49:31 -05:00
commit c4e4035fea
18 changed files with 342 additions and 176 deletions

View File

@ -29,10 +29,6 @@ class AppComposer
$mailAddress = env('MAIL_ADDRESS', false);
$mailFrom = env('MAIL_NAME', false);
$withData = [
'subscribersEnabled' => $isEnabled && $mailAddress && $mailFrom,
];
$view->with($withData);
$view->withSubscribersEnabled($isEnabled && $mailAddress && $mailFrom);
}
}

View File

@ -25,15 +25,16 @@ class ThemeComposer
*/
public function compose(View $view)
{
$view->withThemeBackgroundColor(Setting::get('style_background_color'));
$view->withThemeTextColor(Setting::get('style_text_color'));
$viewData = $view->getData();
$themeView = array_only($viewData, preg_grep('/^theme/', array_keys($viewData)));
$hasThemeSettings = array_filter($themeView, function ($data) {
return $data != null;
});
$view->withThemeSetup(!empty($hasThemeSettings));
// Theme colors.
$view->withThemeBackgroundColor(Setting::get('style_background_color', '#F0F3F4'));
$view->withThemeBackgroundFills(Setting::get('style_background_fills', '#FFFFFF'));
$view->withThemeTextColor(Setting::get('style_text_color', '#333333'));
$view->withThemeReds(Setting::get('style_reds', '#ff6f6f'));
$view->withThemeBlues(Setting::get('style_blues', '#3498db'));
$view->withThemeGreens(Setting::get('style_greens', '#7ED321'));
$view->withThemeYellows(Setting::get('style_yellows', '#F7CA18'));
$view->withThemeOranges(Setting::get('style_oranges', '#FF8800'));
$view->withThemeMetrics(Setting::get('style_metrics', '#0dccc0'));
$view->withThemeLinks(Setting::get('style_links', '#7ED321'));
}
}

View File

@ -67,3 +67,31 @@ if (!function_exists('subscribers_enabled')) {
return $isEnabled && $mailAddress && $mailFrom;
}
}
if (!function_exists('color_darken')) {
/**
* Darken a color.
*
* @param string $hex
* @param int $percent
*
* @return string
*/
function color_darken($hex, $percent)
{
$hex = preg_replace('/[^0-9a-f]/i', '', $hex);
$new_hex = '#';
if (strlen($hex) < 6) {
$hex = $hex[0] + $hex[0] + $hex[1] + $hex[1] + $hex[2] + $hex[2];
}
for ($i = 0; $i < 3; $i++) {
$dec = hexdec(substr($hex, $i * 2, 2));
$dec = min(max(0, $dec + $dec * $percent), 255);
$new_hex .= str_pad(dechex($dec), 2, 0, STR_PAD_LEFT);
}
return $new_hex;
}
}

View File

@ -17,6 +17,23 @@ class ComponentPresenter extends AbstractPresenter
{
use TimestampsTrait;
/**
* Returns the override class name for theming.
*
* @return string
*/
public function status_color()
{
$newStatus = '';
switch ($this->wrappedObject->status) {
case 1: return 'greens';
case 2: return 'blues';
case 3: return 'yellows';
case 4: return 'reds';
}
}
/**
* Convert the presenter instance to an array.
*

View File

@ -168,13 +168,13 @@ class IncidentPresenter extends AbstractPresenter
case 0: // Scheduled
return 'icon ion-android-calendar';
case 1: // Investigating
return 'icon ion-flag';
return 'icon ion-flag oranges';
case 2: // Identified
return 'icon ion-alert';
return 'icon ion-alert yellows';
case 3: // Watching
return 'icon ion-eye';
return 'icon ion-eye blues';
case 4: // Fixed
return 'icon ion-checkmark';
return 'icon ion-checkmark greens';
default: // Something actually broke, this shouldn't happen.
return '';
}

View File

@ -34,7 +34,7 @@ class ComposerServiceProvider extends ServiceProvider
$factory->composer('*', CurrentUserComposer::class);
$factory->composer(['index'], MetricsComposer::class);
$factory->composer(['index', 'incident', 'subscribe'], StatusPageComposer::class);
$factory->composer(['index', 'incident', 'subscribe'], ThemeComposer::class);
$factory->composer(['index', 'incident', 'subscribe', 'dashboard.settings.theme'], ThemeComposer::class);
$factory->composer('dashboard.*', DashboardComposer::class);
$factory->composer(['setup', 'dashboard.settings.app-setup'], TimezoneLocaleComposer::class);
}

View File

@ -27,6 +27,10 @@ body.status-page {
}
}
p, strong {
color: $cachet-base-dark;
}
.tooltip {
.tooltip-inner {
padding: 8px 12px;
@ -269,7 +273,6 @@ body.status-page {
}
&.group-name {
font-size: 1.2em;
background-color: $cachet_gray_light;
padding: {
top: 0.6em;

View File

@ -88,7 +88,7 @@ return [
],
// Other
'powered_by' => ':app Status Page is powered by <a href="https://cachethq.io">Cachet</a>.',
'powered_by' => ':app Status Page is powered by <a href="https://cachethq.io" class="links">Cachet</a>.',
'about_this_site' => 'About this site',
'rss-feed' => 'RSS',
'atom-feed' => 'Atom',

View File

@ -117,8 +117,16 @@ return [
],
'theme' => [
'background-color' => 'Background Color',
'background-fills' => 'Background Fills (Components, Incidents, Footer)',
'text-color' => 'Text Color',
'dashboard-login' => 'Show dashboard button in the footer?',
'reds' => 'Red (Used for errors)',
'blues' => 'Blue (Used for information)',
'greens' => 'Green (Used for success)',
'yellows' => 'Yellow (Used for alerts)',
'oranges' => 'Orange (Used for notices)',
'metrics' => 'Metrics Fill',
'links' => 'Links',
],
],

View File

@ -21,16 +21,74 @@
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.background-color') }}</label>
<input type="text" class="form-control color-code" name="style.background_color" value="{{ Setting::get('style_background_color') }}">
<input type="text" class="form-control color-code" name="style.background_color" value="{{ $theme_background_color }}">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.text-color') }}</label>
<input type="text" class="form-control color-code" name="style.text_color" value="{{ Setting::get('style_text_color') }}">
<input type="text" class="form-control color-code" name="style.text_color" value="{{ $theme_text_color }}">
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.reds') }}</label>
<input type="text" class="form-control color-code" name="style.reds" value="{{ $theme_reds }}">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.blues') }}</label>
<input type="text" class="form-control color-code" name="style.blues" value="{{ $theme_blues }}">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.greens') }}</label>
<input type="text" class="form-control color-code" name="style.greens" value="{{ $theme_greens }}">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.yellows') }}</label>
<input type="text" class="form-control color-code" name="style.yellows" value="{{ $theme_yellows }}">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.oranges') }}</label>
<input type="text" class="form-control color-code" name="style.oranges" value="{{ $theme_oranges }}">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.metrics') }}</label>
<input type="text" class="form-control color-code" name="style.metrics" value="{{ $theme_metrics }}">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.links') }}</label>
<input type="text" class="form-control color-code" name="style.links" value="{{ $theme_links }}">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>{{ trans('forms.settings.theme.background-fills') }}</label>
<input type="text" class="form-control color-code" name="style.background_fills" value="{{ $theme_background_fills }}">
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
@ -39,28 +97,6 @@
</div>
</div>
</div>
{{--
<div class="row">
<div class="col-xs-4">
<div class="form-group">
<label>Success Warning Color</label>
<input type="text" class="form-control color-code" name="style.success_warning_color" value="{{ Setting::get('style_success_warning_color') }}">
</div>
</div>
<div class="col-xs-4">
<div class="form-group">
<label>Error Warning Color</label>
<input type="text" class="form-control color-code" name="style.error_warning_color" value="{{ Setting::get('style_error_warning_color') }}">
</div>
</div>
<div class="col-xs-4">
<div class="form-group">
<label>Info Warning Color</label>
<input type="text" class="form-control color-code" name="style.style_info_warning_color" value="{{ Setting::get('style_info_warning_color') }}">
</div>
</div>
</div>
--}}
</fieldset>
<div class="row">

View File

@ -1,48 +1,48 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
@if($subscribersEnabled)
<p><a class="btn btn-success btn-outline" href="{{ route('subscribe.subscribe') }}">{{ trans('cachet.subscriber.button') }}</a></p>
@endif
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
<div class="section-messages">
@include('dashboard.partials.errors')
</div>
@if($bannerImage = Setting::get('app_banner'))
<div class="row app-banner">
<div class="col-md-12 text-center">
<?php $bannerType = Setting::get('app_banner_type') ?>
@if($app_url = Setting::get('app_domain'))
<a href="{{ $app_url }}"><img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive">
@endif
</div>
</div>
<div class="pull-right">
@if($subscribers_enabled)
<p><a class="btn btn-success btn-outline links" href="{{ route('subscribe.subscribe') }}">{{ trans('cachet.subscriber.button') }}</a></p>
@endif
<p><a class="btn btn-success btn-outline links" href="/"><i class="ion-home"></i></a></p>
</div>
<h4>{{ formatted_date($incident->created_at) }}</h4>
<div class="clearfix"></div>
<div class="timeline">
<div class="content-wrapper">
<div class="moment first">
<div class="row event clearfix">
<div class="col-sm-1">
<div class="status-icon status-{{ $incident->status }}" data-toggle="tooltip" title="{{ $incident->humanStatus }}" data-placement="left">
<i class="{{ $incident->icon }}"></i>
</div>
</div>
<div class="col-xs-10 col-xs-offset-2 col-sm-11 col-sm-offset-0">
@include('partials.incident', ['incident' => $incident, 'with_link' => false])
<div class="section-messages">
@include('dashboard.partials.errors')
</div>
@if($bannerImage = Setting::get('app_banner'))
<div class="row app-banner">
<div class="col-md-12 text-center">
<?php $bannerType = Setting::get('app_banner_type') ?>
@if($app_url = Setting::get('app_domain'))
<a href="{{ $app_url }}" class="links"><img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive">
@endif
</div>
</div>
@endif
<h4>{{ formatted_date($incident->created_at) }}</h4>
<div class="timeline">
<div class="content-wrapper">
<div class="moment first">
<div class="row event clearfix">
<div class="col-sm-1">
<div class="status-icon status-{{ $incident->status }}" data-toggle="tooltip" title="{{ $incident->humanStatus }}" data-placement="left">
<i class="{{ $incident->icon }}"></i>
</div>
</div>
<div class="col-xs-10 col-xs-offset-2 col-sm-11 col-sm-offset-0">
@include('partials.incident', ['incident' => $incident, 'with_link' => false])
</div>
</div>
</div>
</div>
</div>
@stop

View File

@ -1,85 +1,85 @@
@extends('layout.master')
@section('content')
@if($subscribersEnabled)
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="{{ route('subscribe.subscribe') }}">{{ trans('cachet.subscriber.button') }}</a></p>
@if($subscribers_enabled)
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="{{ route('subscribe.subscribe') }}">{{ trans('cachet.subscriber.button') }}</a></p>
</div>
@endif
<div class="clearfix"></div>
<div class="section-messages">
@include('dashboard.partials.errors')
</div>
@if($bannerImage = Setting::get('app_banner'))
<div class="row app-banner">
<div class="col-md-12 text-center">
<?php $bannerType = Setting::get('app_banner_type') ?>
@if($app_url = Setting::get('app_domain'))
<a href="{{ $app_url }}"><img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive">
@endif
</div>
@endif
</div>
@endif
<div class="clearfix"></div>
<div class="section-status">
<div class="alert alert-{{ $systemStatus }}">{{ $systemMessage }}</div>
</div>
<div class="section-messages">
@include('dashboard.partials.errors')
</div>
@if($about_app)
<div class="about-app">
<h1>{{ trans('cachet.about_this_site') }}</h1>
<p>{!! $about_app !!}</p>
</div>
@endif
@if($bannerImage = Setting::get('app_banner'))
<div class="row app-banner">
<div class="col-md-12 text-center">
<?php $bannerType = Setting::get('app_banner_type') ?>
@if($app_url = Setting::get('app_domain'))
<a href="{{ $app_url }}"><img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive">
@endif
</div>
</div>
@endif
@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty())
<div class="section-components">
@include('partials.components')
</div>
@endif
<div class="section-status">
<div class="alert alert-{{ $systemStatus }}">{{ $systemMessage }}</div>
</div>
@if($display_metrics && Setting::get('display_graphs'))
<div class="section-metrics">
@include('partials.metrics')
</div>
@endif
@if($about_app)
<div class="about-app">
<h1>{{ trans('cachet.about_this_site') }}</h1>
<p>{!! $about_app !!}</p>
</div>
@endif
@if(!$scheduled_maintenance->isEmpty())
<div class="section-scheduled">
@include('partials.schedule')
</div>
@endif
@if(!$component_groups->isEmpty() || !$ungrouped_components->isEmpty())
<div class="section-components">
@include('partials.components')
</div>
@endif
@if($days_to_show > 0)
<div class="section-timeline">
<h1>{{ trans('cachet.incidents.past') }}</h1>
@foreach($all_incidents as $date => $incidents)
@include('partials.incidents', [compact($date), compact($incidents)])
@endforeach
</div>
@if($display_metrics && Setting::get('display_graphs'))
<div class="section-metrics">
@include('partials.metrics')
</div>
@endif
@if(!$scheduled_maintenance->isEmpty())
<div class="section-scheduled">
@include('partials.schedule')
</div>
@endif
@if($days_to_show > 0)
<div class="section-timeline">
<h1>{{ trans('cachet.incidents.past') }}</h1>
@foreach($all_incidents as $date => $incidents)
@include('partials.incidents', [compact($date), compact($incidents)])
@endforeach
</div>
<nav>
<ul class="pager">
@if($can_page_backward)
<li class="previous">
<a href="{{ route('status-page') }}?start_date={{ $previous_date }}">
<span aria-hidden="true">&larr;</span> {{ trans('cachet.incidents.previous_week') }}
</a>
</li>
@endif
@if($can_page_forward)
<li class="next">
<a href="{{ route('status-page') }}?start_date={{ $next_date }}">
{{ trans('cachet.incidents.next_week') }} <span aria-hidden="true">&rarr;</span>
</a>
</li>
@endif
</ul>
</nav>
@endif
<nav>
<ul class="pager">
@if($can_page_backward)
<li class="previous">
<a href="{{ route('status-page') }}?start_date={{ $previous_date }}" class="links">
<span aria-hidden="true">&larr;</span> {{ trans('cachet.incidents.previous_week') }}
</a>
</li>
@endif
@if($can_page_forward)
<li class="next">
<a href="{{ route('status-page') }}?start_date={{ $next_date }}" class="links">
{{ trans('cachet.incidents.next_week') }} <span aria-hidden="true">&rarr;</span>
</a>
</li>
@endif
</ul>
</nav>
@endif
@stop

View File

@ -1,6 +1,6 @@
<li class="list-group-item {{ $component->group_id ? "sub-component" : "component" }}">
@if($component->link)
<a href="{{ $component->link }}" target="_blank">{{ $component->name }}</a>
<a href="{{ $component->link }}" target="_blank" class="links">{{ $component->name }}</a>
@else
{{ $component->name }}
@endif
@ -10,6 +10,6 @@
@endif
<div class="pull-right">
<small class="text-component-{{ $component->status }}">{{ $component->humanStatus }}</small>
<small class="text-component-{{ $component->status }} {{ $component->status_color }}">{{ $component->humanStatus }}</small>
</div>
</li>

View File

@ -1,4 +1,4 @@
<footer>
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-sm-6">

View File

@ -1,4 +1,4 @@
<div class="panel panel-message">
<div class="panel panel-message incident">
<div class="panel-heading">
@if($current_user)
<div class="pull-right btn-group">
@ -13,7 +13,7 @@
<br>
<small class="date">
@if($with_link)
<a href="{{ route('incident', ['id' => $incident->id]) }}"><abbr class="timeago" data-toggle="tooltip" data-placement="right" title="{{ $incident->timestamp_formatted }}" data-timeago="{{ $incident->timestamp_iso }}"></abbr></a>
<a href="{{ route('incident', ['id' => $incident->id]) }}" class="links"><abbr class="timeago" data-toggle="tooltip" data-placement="right" title="{{ $incident->timestamp_formatted }}" data-timeago="{{ $incident->timestamp_iso }}"></abbr></a>
@else
<abbr class="timeago" data-toggle="tooltip" data-placement="right" title="{{ $incident->timestamp_formatted }}" data-timeago="{{ $incident->timestamp_iso }}"></abbr>
@endif

View File

@ -15,7 +15,7 @@
</div>
</div>
@empty
<div class="panel panel-message">
<div class="panel panel-message incident">
<div class="panel-body">
<p>{{ trans('cachet.incidents.none') }}</p>
</div>

View File

@ -1,15 +1,15 @@
@if($metrics->count() > 0)
<ul class="list-group metrics">
<ul class="list-group">
@foreach($metrics as $metric)
<li class="list-group-item metric" data-metric-id="{{ $metric->id }}">
<div class="row">
<div class="col-xs-10">
<h4>
<strong>
{{ $metric->name }}
@if($metric->description)
<i class="ion ion-ios-help-outline" data-toggle="tooltip" data-title="{{ $metric->description }}"></i>
@endif
</h4>
</strong>
</div>
<div class="col-xs-2">
<div class="dropdown pull-right">
@ -45,12 +45,13 @@
showTooltips: false,
labels: [],
datasets: [{
fillColor: "rgba(220,220,220,0.1)",
strokeColor: "rgba(52,152,219,0.6)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
// fillColor: "rgba(220,220,220,0.1)",
fillColor: "{{$theme_metrics}}",
// strokeColor: "{{ $theme_metrics }}",
pointColor: "{{ color_darken($theme_metrics, -0.1) }}",
pointStrokeColor: "{{ color_darken($theme_metrics, -0.1) }}",
pointHighlightFill: "{{ color_darken($theme_metrics, -0.2) }}",
pointHighlightStroke: "{{ color_darken($theme_metrics, -0.2) }}",
data: []
}],
};

View File

@ -1,12 +1,88 @@
@if($theme_setup)
<style type="text/css">
body.status-page {
@if($theme_background_color)
background-color: {{ $theme_background_color }};
@endif
@if($theme_text_color)
color: {{ $theme_text_color }};
@endif
}
.reds { color: {{ $theme_reds }} !important; }
.blues { color: {{ $theme_blues }} !important; }
.greens { color: {{ $theme_greens }} !important; }
.yellows { color: {{ $theme_yellows }} !important; }
.oranges { color: {{ $theme_oranges }} !important; }
.metrics { color: {{ $theme_metrics }} !important; }
.links { color: {{ $theme_links }} !important; }
/**
* Alert overrides.
*/
.alert {
background-color: {{ $theme_yellows }} !important;
border-color: {{ color_darken($theme_yellows, -0.1) }} !important;
}
.alert.alert-success {
background-color: {{ $theme_greens }} !important;
border-color: {{ color_darken($theme_greens, -0.1) }} !important;
}
.alert.alert-info {
background-color: {{ $theme_blues }} !important;
border-color: {{ color_darken($theme_blues, -0.1) }} !important;
}
.alert.alert-danger {
background-color: {{ $theme_reds }} !important;
border-color: {{ color_darken($theme_reds, -0.1) }} !important;
}
/**
* Button Overrides
*/
.btn.links {
color: {{ color_darken($theme_yellows, -0.3) }} !important
}
.btn.btn-success {
background-color: {{ $theme_greens }} !important;
border-color: {{ color_darken($theme_greens, -0.1) }} !important;
}
.btn.btn-success.links {
color: {{ color_darken($theme_greens, -0.3) }} !important
}
.btn.btn-info {
background-color: {{ $theme_blues }} !important;
border-color: {{ color_darken($theme_blues, -0.1) }} !important;
}
.btn.btn-info.links {
color: {{ color_darken($theme_blues, -0.3) }} !important
}
.btn.btn-danger {
background-color: {{ $theme_reds }} !important;
border-color: {{ color_darken($theme_reds, -0.1) }} !important;
}
.btn.btn-danger.links {
color: {{ color_darken($theme_reds, -0.3) }} !important
}
/**
* Background fills Overrides
*/
.component {
background-color: {{ $theme_background_fills }} !important;
border-color: {{ color_darken($theme_background_fills, -0.1) }} !important;
}
.sub-component {
background-color: {{ $theme_background_fills }} !important;
border-color: {{ color_darken($theme_background_fills, -0.1) }} !important;
}
.incident {
background-color: {{ $theme_background_fills }} !important;
border-color: {{ color_darken($theme_background_fills, -0.1) }} !important;
}
.status-icon {
background-color: {{ $theme_background_fills }} !important;
border-color: {{ color_darken($theme_background_fills, -0.1) }} !important;
}
.panel.panel-message:after {
border-left-color: {{ $theme_background_fills }} !important;
border-right-color: {{ $theme_background_fills }} !important;
}
.footer {
background-color: {{ $theme_background_fills }} !important;
}
</style>
@endif